All checks were successful
continuous-integration/drone/push Build is passing
344 lines
10 KiB
Rust
344 lines
10 KiB
Rust
use crate::actors::vnc_handler;
|
|
use crate::actors::vnc_tokens_actor::VNCTokensManager;
|
|
use crate::controllers::{HttpResult, LibVirtReq};
|
|
use crate::libvirt_lib_structures::domain::DomainState;
|
|
use crate::libvirt_lib_structures::XMLUuid;
|
|
use crate::libvirt_rest_structures::vm::VMInfo;
|
|
use actix_web::{rt, web, HttpRequest, HttpResponse};
|
|
use std::path::Path;
|
|
use tokio::net::UnixStream;
|
|
|
|
#[derive(serde::Serialize)]
|
|
struct VMInfoAndState {
|
|
#[serde(flatten)]
|
|
info: VMInfo,
|
|
state: DomainState,
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
struct VMUuid {
|
|
uuid: XMLUuid,
|
|
}
|
|
|
|
/// Create a new VM
|
|
pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult {
|
|
let domain = match req.0.as_domain() {
|
|
Ok(d) => d,
|
|
Err(e) => {
|
|
log::error!("Failed to extract domain info! {e}");
|
|
return Ok(
|
|
HttpResponse::BadRequest().json(format!("Failed to extract domain info!\n{e}"))
|
|
);
|
|
}
|
|
};
|
|
let id = match client.update_domain(req.0, domain).await {
|
|
Ok(i) => i,
|
|
Err(e) => {
|
|
log::error!("Failed to update domain info! {e}");
|
|
return Ok(
|
|
HttpResponse::InternalServerError().json(format!("Failed to create domain!\n{e}"))
|
|
);
|
|
}
|
|
};
|
|
|
|
Ok(HttpResponse::Ok().json(VMUuid { uuid: id }))
|
|
}
|
|
|
|
/// Get the list of domains
|
|
pub async fn list_all(client: LibVirtReq) -> HttpResult {
|
|
let list = match client.get_full_domains_list().await {
|
|
Err(e) => {
|
|
log::error!("Failed to get the list of domains! {e}");
|
|
return Ok(HttpResponse::InternalServerError()
|
|
.json(format!("Failed to get the list of domains! {e}")));
|
|
}
|
|
Ok(l) => l,
|
|
};
|
|
let mut out = Vec::with_capacity(list.len());
|
|
|
|
for entry in list {
|
|
let info = VMInfo::from_domain(entry)?;
|
|
out.push(VMInfoAndState {
|
|
state: client
|
|
.get_domain_state(info.uuid.expect("Domain without UUID !"))
|
|
.await?,
|
|
info,
|
|
})
|
|
}
|
|
|
|
Ok(HttpResponse::Ok().json(out))
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct SingleVMUUidReq {
|
|
uid: XMLUuid,
|
|
}
|
|
|
|
/// Get the information about a single VM
|
|
pub async fn get_single(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
let info = match client.get_single_domain(id.uid).await {
|
|
Ok(i) => i,
|
|
Err(e) => {
|
|
log::error!("Failed to get domain info! {e}");
|
|
return Ok(HttpResponse::InternalServerError().json(e.to_string()));
|
|
}
|
|
};
|
|
|
|
log::debug!("INFO={info:#?}");
|
|
|
|
let state = client.get_domain_state(id.uid).await?;
|
|
|
|
Ok(HttpResponse::Ok().json(VMInfoAndState {
|
|
info: VMInfo::from_domain(info)?,
|
|
state,
|
|
}))
|
|
}
|
|
|
|
/// Get the XML configuration of a VM
|
|
pub async fn get_single_src_def(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
let info = match client.get_single_domain_xml(id.uid).await {
|
|
Ok(i) => i,
|
|
Err(e) => {
|
|
log::error!("Failed to get domain source XML! {e}");
|
|
return Ok(HttpResponse::InternalServerError().json(e.to_string()));
|
|
}
|
|
};
|
|
|
|
Ok(HttpResponse::Ok()
|
|
.content_type("application/xml")
|
|
.body(info))
|
|
}
|
|
|
|
/// Update a VM information
|
|
pub async fn update(
|
|
client: LibVirtReq,
|
|
id: web::Path<SingleVMUUidReq>,
|
|
req: web::Json<VMInfo>,
|
|
) -> HttpResult {
|
|
let mut domain = match req.0.as_domain() {
|
|
Ok(d) => d,
|
|
Err(e) => {
|
|
log::error!("Failed to extract domain info! {e}");
|
|
return Ok(
|
|
HttpResponse::BadRequest().json(format!("Failed to extract domain info! {e}"))
|
|
);
|
|
}
|
|
};
|
|
|
|
domain.uuid = Some(id.uid);
|
|
if let Err(e) = client.update_domain(req.0, domain).await {
|
|
log::error!("Failed to update domain info! {e}");
|
|
return Ok(HttpResponse::BadRequest().json(format!("Failed to update domain info!\n{e}")));
|
|
}
|
|
|
|
Ok(HttpResponse::Ok().finish())
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
pub struct VMAutostart {
|
|
autostart: bool,
|
|
}
|
|
|
|
/// Get autostart value of a vm
|
|
pub async fn get_autostart(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(HttpResponse::Ok().json(VMAutostart {
|
|
autostart: client.is_domain_autostart(id.uid).await?,
|
|
}))
|
|
}
|
|
|
|
/// Configure autostart value for a vm
|
|
pub async fn set_autostart(
|
|
client: LibVirtReq,
|
|
id: web::Path<SingleVMUUidReq>,
|
|
body: web::Json<VMAutostart>,
|
|
) -> HttpResult {
|
|
client.set_domain_autostart(id.uid, body.autostart).await?;
|
|
Ok(HttpResponse::Accepted().json("OK"))
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct DeleteVMQuery {
|
|
keep_files: bool,
|
|
}
|
|
|
|
/// Delete a VM
|
|
pub async fn delete(
|
|
client: LibVirtReq,
|
|
id: web::Path<SingleVMUUidReq>,
|
|
req: web::Json<DeleteVMQuery>,
|
|
) -> HttpResult {
|
|
if let Err(e) = client.kill_domain(id.uid).await {
|
|
log::info!("Failed to kill domain before deleting it: {e}");
|
|
}
|
|
|
|
client
|
|
.delete_domain(id.uid, req.keep_files)
|
|
.await
|
|
.map_err(|e| {
|
|
log::error!("Failed to delete domain! {e}");
|
|
HttpResponse::InternalServerError().body(e.to_string())
|
|
})?;
|
|
|
|
Ok(HttpResponse::Ok().finish())
|
|
}
|
|
|
|
/// Start a VM
|
|
pub async fn start(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(match client.start_domain(id.uid).await {
|
|
Ok(_) => HttpResponse::Ok().json("Domain started"),
|
|
Err(e) => {
|
|
log::error!("Failed to start domain {:?} ! {e}", id.uid);
|
|
HttpResponse::InternalServerError().json(format!("Failed to start domain! - {e}"))
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Shutdown a VM
|
|
pub async fn shutdown(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(match client.shutdown_domain(id.uid).await {
|
|
Ok(_) => HttpResponse::Ok().json("Domain shutdown"),
|
|
Err(e) => {
|
|
log::error!("Failed to shutdown domain {:?} ! {e}", id.uid);
|
|
HttpResponse::InternalServerError().json("Failed to shutdown domain!")
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Kill a VM
|
|
pub async fn kill(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(match client.kill_domain(id.uid).await {
|
|
Ok(_) => HttpResponse::Ok().json("Domain killed"),
|
|
Err(e) => {
|
|
log::error!("Failed to kill domain {:?} ! {e}", id.uid);
|
|
HttpResponse::InternalServerError().json("Failed to kill domain!")
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Reset a VM
|
|
pub async fn reset(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(match client.reset_domain(id.uid).await {
|
|
Ok(_) => HttpResponse::Ok().json("Domain reseted"),
|
|
Err(e) => {
|
|
log::error!("Failed to reset domain {:?} ! {e}", id.uid);
|
|
HttpResponse::InternalServerError().json("Failed to reset domain!")
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Suspend a VM
|
|
pub async fn suspend(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(match client.suspend_domain(id.uid).await {
|
|
Ok(_) => HttpResponse::Ok().json("Domain suspended"),
|
|
Err(e) => {
|
|
log::error!("Failed to suspend domain {:?} ! {e}", id.uid);
|
|
HttpResponse::InternalServerError().json("Failed to suspend domain!")
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Resume a VM
|
|
pub async fn resume(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(match client.resume_domain(id.uid).await {
|
|
Ok(_) => HttpResponse::Ok().json("Domain resumed"),
|
|
Err(e) => {
|
|
log::error!("Failed to resume domain {:?} ! {e}", id.uid);
|
|
HttpResponse::InternalServerError().json("Failed to resume domain!")
|
|
}
|
|
})
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
struct DomainStateRes {
|
|
state: DomainState,
|
|
}
|
|
|
|
/// Get the state of a VM
|
|
pub async fn state(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(match client.get_domain_state(id.uid).await {
|
|
Ok(s) => HttpResponse::Ok().json(DomainStateRes { state: s }),
|
|
Err(e) => {
|
|
log::error!("Failed to get domain state {:?} ! {e}", id.uid);
|
|
HttpResponse::InternalServerError().json("Failed to get domain state!")
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Take a screenshot of a VM
|
|
pub async fn screenshot(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
|
Ok(match client.screenshot_domain(id.uid).await {
|
|
Ok(b) => HttpResponse::Ok().content_type("image/png").body(b),
|
|
Err(e) => {
|
|
log::error!(
|
|
"Failed to take the screenshot of a domain {:?} ! {e}",
|
|
id.uid
|
|
);
|
|
HttpResponse::InternalServerError().json("Failed to take the screenshot of the domain!")
|
|
}
|
|
})
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
struct IssueVNCTokenResponse {
|
|
token: String,
|
|
}
|
|
|
|
/// Issue a VNC connection token
|
|
pub async fn vnc_token(
|
|
manager: web::Data<VNCTokensManager>,
|
|
id: web::Path<SingleVMUUidReq>,
|
|
) -> HttpResult {
|
|
Ok(match manager.issue_token(id.uid).await {
|
|
Ok(token) => HttpResponse::Ok().json(IssueVNCTokenResponse { token }),
|
|
Err(e) => {
|
|
log::error!(
|
|
"Failed to issue a token for a domain domain {:?} ! {e}",
|
|
id.uid
|
|
);
|
|
HttpResponse::InternalServerError().json("Failed to issue a token for the domain!")
|
|
}
|
|
})
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct VNCTokenQuery {
|
|
token: String,
|
|
}
|
|
|
|
/// Start a VNC connection
|
|
pub async fn vnc(
|
|
client: LibVirtReq,
|
|
manager: web::Data<VNCTokensManager>,
|
|
token: web::Query<VNCTokenQuery>,
|
|
req: HttpRequest,
|
|
stream: web::Payload,
|
|
) -> HttpResult {
|
|
let domain_id = manager.use_token(token.0.token).await?;
|
|
let domain = client.get_single_domain(domain_id).await?;
|
|
|
|
let socket_path = match domain.devices.graphics {
|
|
None => {
|
|
log::error!("Attempted to open VNC for a domain where VNC is disabled!");
|
|
return Ok(HttpResponse::ServiceUnavailable().json("VNC is not enabled!"));
|
|
}
|
|
Some(g) => g.socket,
|
|
};
|
|
|
|
log::info!("Start VNC connection on socket {socket_path}");
|
|
|
|
let socket_path = Path::new(&socket_path);
|
|
if !socket_path.exists() {
|
|
log::error!("VNC socket path {socket_path:?} does not exist!");
|
|
return Ok(HttpResponse::ServiceUnavailable().json("VNC socket path does not exists!"));
|
|
}
|
|
|
|
let socket = UnixStream::connect(socket_path).await?;
|
|
|
|
let (res, session, msg_stream) = actix_ws::handle(&req, stream)?;
|
|
|
|
// spawn websocket handler (and don't await it) so that the response is returned immediately
|
|
rt::spawn(vnc_handler::handle(session, msg_stream, socket));
|
|
|
|
Ok(res)
|
|
}
|