Can query hypervisor information

This commit is contained in:
Pierre HUBERT 2023-09-06 18:54:38 +02:00
parent fbe11af121
commit 57c023b45b
14 changed files with 253 additions and 9 deletions

View File

@ -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

View File

@ -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]]

View File

@ -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"] }
@ -24,4 +25,5 @@ anyhow = "1.0.75"
actix-multipart = "0.6.1"
tempfile = "3.8.0"
reqwest = { version = "0.11.18", features = ["stream"] }
url = "2.4.0"
url = "2.4.0"
virt = "0.3.0"

View 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,
},
})
}
}

View File

@ -0,0 +1 @@
pub mod libvirt_actor;

View File

@ -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! {

View File

@ -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>;

View File

@ -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?,
}))
}

View File

@ -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;

View 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?
}
}

View File

@ -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",

View File

@ -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;
}
}

View File

@ -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";

View File

@ -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 (