Can query hypervisor information
This commit is contained in:
parent
fbe11af121
commit
57c023b45b
23
README.md
23
README.md
@ -1,2 +1,25 @@
|
||||
# VirtWEB
|
||||
WIP project
|
||||
|
||||
## Development requirements
|
||||
1. The `libvirt-dev` package must be installed:
|
||||
|
||||
```bash
|
||||
sudo apt install libvirt-dev
|
||||
```
|
||||
|
||||
2. Libvirt must also be installed:
|
||||
```bash
|
||||
sudo apt install qemu-kvm libvirt-daemon-system
|
||||
```
|
||||
|
||||
3. Allow the current user to manage VMs:
|
||||
```
|
||||
sudo adduser $USER libvirt
|
||||
```
|
||||
|
||||
> Note: You will need to login again for this change to take effect.
|
||||
|
||||
|
||||
## Production requirements
|
||||
TODO
|
||||
|
77
virtweb_backend/Cargo.lock
generated
77
virtweb_backend/Cargo.lock
generated
@ -2,6 +2,31 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "actix"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cba56612922b907719d4a01cf11c8d5b458e7d3dba946d0435f20f58d6795ed2"
|
||||
dependencies = [
|
||||
"actix-macros",
|
||||
"actix-rt",
|
||||
"actix_derive",
|
||||
"bitflags 2.4.0",
|
||||
"bytes",
|
||||
"crossbeam-channel",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-codec"
|
||||
version = "0.5.1"
|
||||
@ -301,6 +326,17 @@ dependencies = [
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix_derive"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.21.0"
|
||||
@ -703,6 +739,25 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@ -2042,6 +2097,26 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "virt"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9316a0df71f1ec209e7ef8ab07097e4181945b245d3348f57de07a3e811e53cf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"virt-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virt-sys"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd39f6e0d0ab3fe7c371fac05c9b6ca72e186f06a2e666fb3b95441091eba2db"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtue"
|
||||
version = "0.0.13"
|
||||
@ -2052,6 +2127,7 @@ checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314"
|
||||
name = "virtweb_backend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-cors",
|
||||
"actix-files",
|
||||
"actix-identity",
|
||||
@ -2071,6 +2147,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"url",
|
||||
"virt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -11,6 +11,7 @@ env_logger = "0.10.0"
|
||||
clap = { version = "4.3.19", features = ["derive", "env"] }
|
||||
light-openid = { version = "1.0.1", features = ["crypto-wrapper"] }
|
||||
lazy_static = "1.4.0"
|
||||
actix = "0.13.1"
|
||||
actix-web = "4"
|
||||
actix-remote-ip = "0.1.0"
|
||||
actix-session = { version = "0.7.2", features = ["cookie-session"] }
|
||||
@ -25,3 +26,4 @@ actix-multipart = "0.6.1"
|
||||
tempfile = "3.8.0"
|
||||
reqwest = { version = "0.11.18", features = ["stream"] }
|
||||
url = "2.4.0"
|
||||
virt = "0.3.0"
|
79
virtweb_backend/src/actors/libvirt_actor.rs
Normal file
79
virtweb_backend/src/actors/libvirt_actor.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use crate::app_config::AppConfig;
|
||||
use actix::{Actor, Context, Handler, Message};
|
||||
use virt::connect::Connect;
|
||||
|
||||
pub struct LibVirtActor {
|
||||
m: Connect,
|
||||
}
|
||||
|
||||
impl LibVirtActor {
|
||||
/// Connect to hypervisor
|
||||
pub async fn connect() -> anyhow::Result<Self> {
|
||||
let hypervisor_uri = AppConfig::get().hypervisor_uri.as_deref().unwrap_or("");
|
||||
log::info!(
|
||||
"Will connect to hypvervisor at address '{}'",
|
||||
hypervisor_uri
|
||||
);
|
||||
let conn = Connect::open(hypervisor_uri)?;
|
||||
|
||||
Ok(Self { m: conn })
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for LibVirtActor {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "anyhow::Result<HypervisorInfo>")]
|
||||
pub struct GetHypervisorInfo;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct HypervisorInfo {
|
||||
pub r#type: String,
|
||||
pub hyp_version: u32,
|
||||
pub lib_version: u32,
|
||||
pub capabilities: String,
|
||||
pub free_memory: u64,
|
||||
pub hostname: String,
|
||||
pub node: HypervisorNodeInfo,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct HypervisorNodeInfo {
|
||||
pub cpu_model: String,
|
||||
/// Memory size in kilobytes
|
||||
pub memory_size: u64,
|
||||
pub number_of_active_cpus: u32,
|
||||
pub cpu_frequency_mhz: u32,
|
||||
pub number_of_numa_cell: u32,
|
||||
pub number_of_cpu_socket_per_node: u32,
|
||||
pub number_of_core_per_sockets: u32,
|
||||
pub number_of_threads_per_sockets: u32,
|
||||
}
|
||||
|
||||
impl Handler<GetHypervisorInfo> for LibVirtActor {
|
||||
type Result = anyhow::Result<HypervisorInfo>;
|
||||
|
||||
fn handle(&mut self, _msg: GetHypervisorInfo, _ctx: &mut Self::Context) -> Self::Result {
|
||||
let node = self.m.get_node_info()?;
|
||||
Ok(HypervisorInfo {
|
||||
r#type: self.m.get_type()?,
|
||||
hyp_version: self.m.get_hyp_version()?,
|
||||
lib_version: self.m.get_lib_version()?,
|
||||
capabilities: self.m.get_capabilities()?,
|
||||
free_memory: self.m.get_free_memory()?,
|
||||
hostname: self.m.get_hostname()?,
|
||||
node: HypervisorNodeInfo {
|
||||
cpu_model: node.model,
|
||||
memory_size: node.memory,
|
||||
number_of_active_cpus: node.cpus,
|
||||
cpu_frequency_mhz: node.mhz,
|
||||
number_of_numa_cell: node.nodes,
|
||||
number_of_cpu_socket_per_node: node.sockets,
|
||||
number_of_core_per_sockets: node.cores,
|
||||
number_of_threads_per_sockets: node.threads,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
1
virtweb_backend/src/actors/mod.rs
Normal file
1
virtweb_backend/src/actors/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod libvirt_actor;
|
@ -72,6 +72,10 @@ pub struct AppConfig {
|
||||
/// Directory where temporary files are stored
|
||||
#[arg(long, env, default_value = "/tmp")]
|
||||
pub temp_dir: String,
|
||||
|
||||
/// Hypervisor URI. If not specified, "" will be used instead
|
||||
#[arg(long, env)]
|
||||
pub hypervisor_uri: Option<String>,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::libvirt_client::LibVirtClient;
|
||||
use actix_web::body::BoxBody;
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io::ErrorKind;
|
||||
@ -78,3 +79,5 @@ impl From<reqwest::header::ToStrError> for HttpErr {
|
||||
}
|
||||
|
||||
pub type HttpResult = Result<HttpResponse, HttpErr>;
|
||||
|
||||
pub type LibVirtReq = web::Data<LibVirtClient>;
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::actors::libvirt_actor::HypervisorInfo;
|
||||
use crate::app_config::AppConfig;
|
||||
use crate::constants;
|
||||
use crate::controllers::{HttpResult, LibVirtReq};
|
||||
use crate::extractors::local_auth_extractor::LocalAuthEnabled;
|
||||
use actix_web::{HttpResponse, Responder};
|
||||
|
||||
@ -23,3 +25,14 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
||||
iso_max_size: constants::ISO_MAX_SIZE,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct ServerInfo {
|
||||
hypervisor: HypervisorInfo,
|
||||
}
|
||||
|
||||
pub async fn server_info(client: LibVirtReq) -> HttpResult {
|
||||
Ok(HttpResponse::Ok().json(ServerInfo {
|
||||
hypervisor: client.get_info().await?,
|
||||
}))
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
pub mod actors;
|
||||
pub mod app_config;
|
||||
pub mod constants;
|
||||
pub mod controllers;
|
||||
pub mod extractors;
|
||||
pub mod libvirt_client;
|
||||
pub mod middlewares;
|
||||
pub mod utils;
|
||||
|
13
virtweb_backend/src/libvirt_client.rs
Normal file
13
virtweb_backend/src/libvirt_client.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::actors::libvirt_actor;
|
||||
use crate::actors::libvirt_actor::{HypervisorInfo, LibVirtActor};
|
||||
use actix::Addr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LibVirtClient(pub Addr<LibVirtActor>);
|
||||
|
||||
impl LibVirtClient {
|
||||
/// Get hypervisor info
|
||||
pub async fn get_info(&self) -> anyhow::Result<HypervisorInfo> {
|
||||
self.0.send(libvirt_actor::GetHypervisorInfo).await?
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
use actix::Actor;
|
||||
use actix_cors::Cors;
|
||||
use actix_identity::config::LogoutBehaviour;
|
||||
use actix_identity::IdentityMiddleware;
|
||||
@ -13,12 +14,14 @@ use actix_web::web::Data;
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use light_openid::basic_state_manager::BasicStateManager;
|
||||
use std::time::Duration;
|
||||
use virtweb_backend::actors::libvirt_actor::LibVirtActor;
|
||||
use virtweb_backend::app_config::AppConfig;
|
||||
use virtweb_backend::constants;
|
||||
use virtweb_backend::constants::{
|
||||
MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME,
|
||||
};
|
||||
use virtweb_backend::controllers::{auth_controller, iso_controller, server_controller};
|
||||
use virtweb_backend::libvirt_client::LibVirtClient;
|
||||
use virtweb_backend::middlewares::auth_middleware::AuthChecker;
|
||||
use virtweb_backend::utils::files_utils;
|
||||
|
||||
@ -29,6 +32,13 @@ async fn main() -> std::io::Result<()> {
|
||||
log::debug!("Create required directory, if missing");
|
||||
files_utils::create_directory_if_missing(&AppConfig::get().iso_storage_path()).unwrap();
|
||||
|
||||
let conn = Data::new(LibVirtClient(
|
||||
LibVirtActor::connect()
|
||||
.await
|
||||
.expect("Failed to connect to hypervisor!")
|
||||
.start(),
|
||||
));
|
||||
|
||||
log::info!("Start to listen on {}", AppConfig::get().listen_address);
|
||||
|
||||
let state_manager = Data::new(BasicStateManager::new());
|
||||
@ -69,6 +79,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.app_data(Data::new(RemoteIPConfig {
|
||||
proxy: AppConfig::get().proxy_ip.clone(),
|
||||
}))
|
||||
.app_data(conn.clone())
|
||||
// Uploaded files
|
||||
.app_data(MultipartFormConfig::default().total_limit(constants::ISO_MAX_SIZE))
|
||||
.app_data(TempFileConfig::default().directory(&AppConfig::get().temp_dir))
|
||||
@ -78,6 +89,10 @@ async fn main() -> std::io::Result<()> {
|
||||
"/api/server/static_config",
|
||||
web::get().to(server_controller::static_config),
|
||||
)
|
||||
.route(
|
||||
"/api/server/info",
|
||||
web::get().to(server_controller::server_info),
|
||||
)
|
||||
// Auth controller
|
||||
.route(
|
||||
"/api/auth/local",
|
||||
|
@ -9,6 +9,8 @@ export interface ServerConfig {
|
||||
|
||||
let config: ServerConfig | null = null;
|
||||
|
||||
export interface ServerInfo {}
|
||||
|
||||
export class ServerApi {
|
||||
/**
|
||||
* Get server configuration
|
||||
@ -29,4 +31,16 @@ export class ServerApi {
|
||||
if (config === null) throw new Error("Missing configuration!");
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server information
|
||||
*/
|
||||
static async SystemInfo(): Promise<ServerInfo> {
|
||||
return (
|
||||
await APIClient.exec({
|
||||
method: "GET",
|
||||
uri: "/server/info",
|
||||
})
|
||||
).data;
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,10 @@ import {
|
||||
} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import * as React from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../../App";
|
||||
import { AuthApi } from "../../api/AuthApi";
|
||||
import { ServerApi } from "../../api/ServerApi";
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { mdiDisc, mdiHome, mdiLanPending } from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import {
|
||||
Box,
|
||||
List,
|
||||
@ -5,13 +7,10 @@ import {
|
||||
ListItemIcon,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
ListSubheader,
|
||||
} from "@mui/material";
|
||||
import { VirtWebAppBar } from "./VirtWebAppBar";
|
||||
import { RouterLink } from "./RouterLink";
|
||||
import { Outlet, useLocation } from "react-router-dom";
|
||||
import Icon from "@mdi/react";
|
||||
import { mdiDisc, mdiHome, mdiLanPending } from "@mdi/js";
|
||||
import { RouterLink } from "./RouterLink";
|
||||
import { VirtWebAppBar } from "./VirtWebAppBar";
|
||||
|
||||
export function BaseAuthenticatedPage(): React.ReactElement {
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user