more progress
This commit is contained in:
parent
b538ff1657
commit
921a791590
16 changed files with 379 additions and 59 deletions
150
README.md
Normal file
150
README.md
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
# vore
|
||||||
|
|
||||||
|
> VFIO Orientated Emulation (_definitely_)
|
||||||
|
|
||||||
|
## What is vore?
|
||||||
|
|
||||||
|
`vore` is a virtual machine management tool focused on VFIO set ups. with a minimal TOML file you should be able to get
|
||||||
|
you should be able to create a VFIO-focused VM.
|
||||||
|
|
||||||
|
It features close integration for this use cases, for example automatic configuration of Looking Glass.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
`vore` loads a TOML file, sends it to the `vored` daemon, which processes it and auto completes required information,
|
||||||
|
and then passes it to a Lua script. this Lua script builds up the qemu command, which then gets started and managed
|
||||||
|
by `vored`.
|
||||||
|
|
||||||
|
`vored` also allows you to save definitions, and `reserve` vfio devices, so that they are claimed at system start up.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Building:
|
||||||
|
|
||||||
|
- Rust
|
||||||
|
- Lua 5.4 (including headers)
|
||||||
|
|
||||||
|
Runtime:
|
||||||
|
|
||||||
|
- Lua 5.4
|
||||||
|
|
||||||
|
## VM Definition
|
||||||
|
|
||||||
|
This is a annotated VM definition with about every option displayed
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[machine]
|
||||||
|
# Name of the VM, this will be the name used internally and externally for the vm
|
||||||
|
name = "win10"
|
||||||
|
# Amount of memory for the virtual machine
|
||||||
|
memory = "12G"
|
||||||
|
# Shorthand for <feature>.enabled = true
|
||||||
|
features = [
|
||||||
|
"uefi",
|
||||||
|
"spice",
|
||||||
|
"pulse",
|
||||||
|
"looking-glass"
|
||||||
|
]
|
||||||
|
# If vore should automatically start this VM when the daemon starts
|
||||||
|
#auto-start = false
|
||||||
|
|
||||||
|
[cpu]
|
||||||
|
# Amount of vCPU's should be given to the
|
||||||
|
amount = 12
|
||||||
|
# If any of the following are given, vore will automatically calculate
|
||||||
|
# the amount of vCPU's, however if both are given, vore will verify it's correctness
|
||||||
|
# Amount of threads ever core has
|
||||||
|
# If amount is even or not set, this is set to 2, if odd, it's set to 1
|
||||||
|
#threads = 2
|
||||||
|
# Amount of cores on this die
|
||||||
|
# If amount is even, this is set to amount/2, if odd it's set to amount
|
||||||
|
# If amount is not set this is 2
|
||||||
|
#cores = 6
|
||||||
|
# Amount of dies per socket, defaults to 1
|
||||||
|
#dies = 1
|
||||||
|
# Amount of sockets, defaults to 1
|
||||||
|
#sockets = 1
|
||||||
|
|
||||||
|
# You can add multiple disks by adding more `[[disk]]` entries
|
||||||
|
[[disk]]
|
||||||
|
# Preset used for this disk, defined in qemu.lua,
|
||||||
|
# run `vore disk preset` to list all available presets
|
||||||
|
preset = "nvme"
|
||||||
|
# Path to disk file
|
||||||
|
path = "/dev/disk/by-id/nvme-eui.6479a74530201073"
|
||||||
|
# Type of disk file, will be automatically set,
|
||||||
|
# but vore will tell you if it can't figure it out
|
||||||
|
#disk_type = "raw"
|
||||||
|
|
||||||
|
[[vfio]]
|
||||||
|
# If when this VM is saved, vored should try to automatically
|
||||||
|
# bind it to the vfio-pci driver
|
||||||
|
reserve = true
|
||||||
|
# vendor, device and index (0-indexed!) can be used to select a certain card
|
||||||
|
# this will grab the second GTX 1080 in the system
|
||||||
|
vendor = 0x10de
|
||||||
|
device = 0x1b80
|
||||||
|
index = 1
|
||||||
|
# you can also instead set addr directly
|
||||||
|
# -however- if you set both vore will check if both match and error out if not
|
||||||
|
# this can be helpful when passing through system devices,
|
||||||
|
# which may move after insertion of e.g. nvme drive
|
||||||
|
addr = "0b:00.3"
|
||||||
|
|
||||||
|
# if this device is a graphics card
|
||||||
|
# it'll both set x-vga, and disable QEMU's virtual GPU
|
||||||
|
graphics = true
|
||||||
|
|
||||||
|
# if this device is multifunctional
|
||||||
|
#multifunction = false
|
||||||
|
|
||||||
|
[pulse]
|
||||||
|
# If a pulseaudio backed audio device should be created
|
||||||
|
# using the features shorthand is preferred
|
||||||
|
#enabled = true
|
||||||
|
# Path to PulseAudio native socket
|
||||||
|
# if not specified vore will automatically resolve it
|
||||||
|
#socket-path = ""
|
||||||
|
# To which user's PulseAudio session it should connect
|
||||||
|
# Can be prefixed with # to set an id
|
||||||
|
# Default is #1000, which is the common default user id
|
||||||
|
#user = "#1000"
|
||||||
|
|
||||||
|
[spice]
|
||||||
|
# if spice support should be enabled
|
||||||
|
# using the features shorthand is preferred
|
||||||
|
#enabled = true
|
||||||
|
# on which path the SPICE socket should listen
|
||||||
|
# If not set vore will use /var/lib/vore/instance/<name>/spice.sock
|
||||||
|
#socket-path = "/run/spicy.sock"
|
||||||
|
|
||||||
|
[looking-glass]
|
||||||
|
# if looking-glass support should be enabled
|
||||||
|
# using the features shorthand is preferred
|
||||||
|
#enabled = true
|
||||||
|
# width, height, and bit depth of the screen LG will transfer
|
||||||
|
# this info is used to calculate the required shared memory file size
|
||||||
|
width = 2560
|
||||||
|
height = 1080
|
||||||
|
#bit-depth = 8
|
||||||
|
# Alternatively you can set the buffer size directly
|
||||||
|
# vore will automatically pick the lowest higher or equal to buffer-size
|
||||||
|
# that is a power of 2
|
||||||
|
#buffer-size = 999999
|
||||||
|
# Path to the shared memory file looking-glass should use
|
||||||
|
# if not specified vore will create a path.
|
||||||
|
# this is mostly for in the case you use the kvmfr kernel module
|
||||||
|
#mem-path = "/dev/kvmfr0"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
- [ ] hugepages support
|
||||||
|
- [ ] USB passthrough
|
||||||
|
- [ ] Hot-plug USB via `vore attach <addr>`
|
||||||
|
- [ ] jack audiodev support
|
||||||
|
- [ ] qemu cmdline on request (`vore x qemucmd`)
|
||||||
|
- [ ] Better CPU support and feature assignment
|
||||||
|
- [ ] more control over CPU pinning (now just pickes the fist amount of CPU's)
|
||||||
|
- [ ] Network device configuration
|
|
@ -125,6 +125,10 @@ vore:set_build_command(function(instance, vm)
|
||||||
vm:arg("-spice", "unix,addr=" .. instance.spice.socket_path .. ",disable-ticketing=on,seamless-migration=on")
|
vm:arg("-spice", "unix,addr=" .. instance.spice.socket_path .. ",disable-ticketing=on,seamless-migration=on")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if instance.jack.enabled then
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
if instance.pulse.enabled then
|
if instance.pulse.enabled then
|
||||||
vm:arg("-device", "intel-hda", "-device", "hda-duplex,audiodev=pa0")
|
vm:arg("-device", "intel-hda", "-device", "hda-duplex,audiodev=pa0")
|
||||||
vm:arg("-audiodev", "pa,server=/run/user/1000/pulse/native,id=pa0")
|
vm:arg("-audiodev", "pa,server=/run/user/1000/pulse/native,id=pa0")
|
||||||
|
@ -201,13 +205,13 @@ function ide_disk_gen(name, device_type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
vore:register_disk_preset("ssd", virtio_scsi_disk_gen("ssd"))
|
vore:register_disk_preset("ssd", "virtio based SSD (requires virtio drivers)", virtio_scsi_disk_gen("ssd"))
|
||||||
vore:register_disk_preset("hdd", virtio_scsi_disk_gen("hdd"))
|
vore:register_disk_preset("hdd", "virtio based HDD (requires virtio drivers)", virtio_scsi_disk_gen("hdd"))
|
||||||
|
|
||||||
vore:register_disk_preset("iso", ide_disk_gen("iso", "ide-cd"))
|
vore:register_disk_preset("iso", "IDE based CD", ide_disk_gen("iso", "ide-cd"))
|
||||||
vore:register_disk_preset("ide", ide_disk_gen("ide", "ide-hd"))
|
vore:register_disk_preset("ide", "IDE based HDD (not recommended, useful when missing virtio drivers)", ide_disk_gen("ide", "ide-hd"))
|
||||||
|
|
||||||
vore:register_disk_preset("nvme", function(vm, _, _, disk)
|
vore:register_disk_preset("nvme", "PCIe based NVMe drive", function(vm, _, _, disk)
|
||||||
local nvme_id = vm:get_counter("nvme", 1)
|
local nvme_id = vm:get_counter("nvme", 1)
|
||||||
|
|
||||||
-- see https://blog.christophersmart.com/2019/12/18/kvm-guests-with-emulated-ssd-and-nvme-drives/
|
-- see https://blog.christophersmart.com/2019/12/18/kvm-guests-with-emulated-ssd-and-nvme-drives/
|
||||||
|
|
|
@ -114,8 +114,9 @@ end
|
||||||
----
|
----
|
||||||
---Register a disk preset
|
---Register a disk preset
|
||||||
---@param name string
|
---@param name string
|
||||||
|
---@param description string
|
||||||
---@param cb fun(vm: VM, instance: Instance, idx: number, disk: Disk): VM
|
---@param cb fun(vm: VM, instance: Instance, idx: number, disk: Disk): VM
|
||||||
function vore:register_disk_preset(name, cb)
|
function vore:register_disk_preset(name, description, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
---set_build_command
|
---set_build_command
|
||||||
|
|
|
@ -13,9 +13,7 @@ macro_rules! default_env {
|
||||||
pub const VORE_DIRECTORY: &str = default_env!("VORE_DIRECTORY", "/var/lib/vore");
|
pub const VORE_DIRECTORY: &str = default_env!("VORE_DIRECTORY", "/var/lib/vore");
|
||||||
pub const VORE_SOCKET: &str = default_env!("VORE_SOCKET", "/run/vore.sock");
|
pub const VORE_SOCKET: &str = default_env!("VORE_SOCKET", "/run/vore.sock");
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub const VORE_CONFIG: &str = default_env!(
|
pub const VORE_CONFIG: &str =
|
||||||
"VORE_CONFIG",
|
default_env!("VORE_CONFIG", concat!(env!("PWD"), "/config/vored.toml"));
|
||||||
concat!(file!(), "/../../../../config/vored.toml")
|
|
||||||
);
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
pub const VORE_CONFIG: &str = default_env!("VORE_CONFIG", "/etc/vore/vored.toml");
|
pub const VORE_CONFIG: &str = default_env!("VORE_CONFIG", "/etc/vore/vored.toml");
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::utils::get_uid_by_username;
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
use config::{Config, File, FileFormat, Value};
|
use config::{Config, File, FileFormat, Value};
|
||||||
use serde::de::Visitor;
|
use serde::de::Visitor;
|
||||||
|
@ -140,8 +141,8 @@ pub struct CpuConfig {
|
||||||
impl Default for CpuConfig {
|
impl Default for CpuConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
CpuConfig {
|
CpuConfig {
|
||||||
amount: 2,
|
amount: 4,
|
||||||
cores: 1,
|
cores: 2,
|
||||||
threads: 2,
|
threads: 2,
|
||||||
dies: 1,
|
dies: 1,
|
||||||
sockets: 1,
|
sockets: 1,
|
||||||
|
@ -357,9 +358,13 @@ impl LookingGlassConfig {
|
||||||
// Add additional 2mb
|
// Add additional 2mb
|
||||||
minimum_needed += 2 * 1024 * 1024;
|
minimum_needed += 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
self.set_buffer_size(minimum_needed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_buffer_size(&mut self, wanted: u64) {
|
||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
let mut buffer_size = 1;
|
let mut buffer_size = 1;
|
||||||
while buffer_size < minimum_needed {
|
while buffer_size <= wanted {
|
||||||
i += 1;
|
i += 1;
|
||||||
buffer_size = 2u64.pow(i);
|
buffer_size = 2u64.pow(i);
|
||||||
}
|
}
|
||||||
|
@ -380,7 +385,7 @@ impl LookingGlassConfig {
|
||||||
|
|
||||||
match (table.get("buffer-size").cloned(), table.get("width").cloned(), table.get("height").cloned()) {
|
match (table.get("buffer-size").cloned(), table.get("width").cloned(), table.get("height").cloned()) {
|
||||||
(Some(buffer_size), None, None) => {
|
(Some(buffer_size), None, None) => {
|
||||||
cfg.buffer_size = buffer_size.into_int()? as u64;
|
cfg.set_buffer_size(buffer_size.into_int()? as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
(None, Some(width), Some(height)) => {
|
(None, Some(width), Some(height)) => {
|
||||||
|
@ -618,16 +623,42 @@ impl VfioConfig {
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
pub struct PulseConfig {
|
pub struct PulseConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
pub socket_path: String,
|
||||||
|
pub user: String,
|
||||||
|
pub user_uid: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PulseConfig {
|
impl PulseConfig {
|
||||||
pub fn from_table(table: HashMap<String, Value>) -> Result<PulseConfig, anyhow::Error> {
|
pub fn from_table(table: HashMap<String, Value>) -> Result<PulseConfig, anyhow::Error> {
|
||||||
let mut cfg = PulseConfig { enabled: false };
|
let mut cfg = PulseConfig {
|
||||||
|
enabled: false,
|
||||||
|
socket_path: "".to_string(),
|
||||||
|
user: "#1000".to_string(),
|
||||||
|
user_uid: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(enabled) = table.get("enabled").cloned() {
|
if let Some(enabled) = table.get("enabled").cloned() {
|
||||||
cfg.enabled = enabled.into_bool()?;
|
cfg.enabled = enabled.into_bool()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(socket_path) = table.get("socket-path").cloned() {
|
||||||
|
cfg.socket_path = socket_path.into_str()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user) = table.get("user").cloned() {
|
||||||
|
cfg.user = user.into_str()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.socket_path.is_empty() {
|
||||||
|
if let Some(number) = cfg.user.strip_prefix('#') {
|
||||||
|
cfg.user_uid = u32::from_str(number).with_context(|| {
|
||||||
|
format!("Couldn't parse {} as number (for pulse.user)", number)
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
cfg.user_uid = get_uid_by_username(&cfg.user)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
pub mod consts;
|
||||||
|
mod cpu_list;
|
||||||
mod global_config;
|
mod global_config;
|
||||||
mod instance_config;
|
mod instance_config;
|
||||||
mod qemu;
|
mod qemu;
|
||||||
mod virtual_machine;
|
|
||||||
mod cpu_list;
|
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
pub mod consts;
|
pub mod utils;
|
||||||
|
mod virtual_machine;
|
||||||
mod virtual_machine_info;
|
mod virtual_machine_info;
|
||||||
|
|
||||||
pub use global_config::*;
|
pub use global_config::*;
|
||||||
|
@ -16,10 +17,11 @@ pub use virtual_machine_info::*;
|
||||||
|
|
||||||
pub fn init_logging() {
|
pub fn init_logging() {
|
||||||
let mut builder = pretty_env_logger::formatted_timed_builder();
|
let mut builder = pretty_env_logger::formatted_timed_builder();
|
||||||
#[cfg(debug_assertions)] {
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
builder.filter_level(LevelFilter::Debug);
|
builder.filter_level(LevelFilter::Debug);
|
||||||
}
|
}
|
||||||
builder.parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| "".to_string()));
|
builder.parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| "".to_string()));
|
||||||
builder.init();
|
builder.init();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ use mlua::{
|
||||||
use serde::ser::Error;
|
use serde::ser::Error;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex, Weak};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
|
use std::{fs, mem};
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Clone)]
|
#[derive(Debug, Default, Deserialize, Clone)]
|
||||||
struct VirtualMachine {
|
struct VirtualMachine {
|
||||||
|
@ -97,10 +97,16 @@ pub struct VoreLuaWeakStorage(Weak<Mutex<VoreLuaStorageInner>>);
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VoreLuaStorageInner {
|
pub struct VoreLuaStorageInner {
|
||||||
build_command: Option<RegistryKey>,
|
build_command: Option<RegistryKey>,
|
||||||
disk_presets: HashMap<String, RegistryKey>,
|
disk_presets: HashMap<String, VoreLuaDiskPreset>,
|
||||||
working_dir: PathBuf,
|
working_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VoreLuaDiskPreset {
|
||||||
|
description: String,
|
||||||
|
callback: RegistryKey,
|
||||||
|
}
|
||||||
|
|
||||||
impl UserData for VoreLuaWeakStorage {
|
impl UserData for VoreLuaWeakStorage {
|
||||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_method("set_build_command", |l, weak, func: Function| {
|
methods.add_method("set_build_command", |l, weak, func: Function| {
|
||||||
|
@ -122,7 +128,7 @@ impl UserData for VoreLuaWeakStorage {
|
||||||
|
|
||||||
methods.add_method(
|
methods.add_method(
|
||||||
"register_disk_preset",
|
"register_disk_preset",
|
||||||
|lua, weak, args: (mlua::String, Function)| {
|
|lua, weak, args: (mlua::String, mlua::String, Function)| {
|
||||||
let strong = weak
|
let strong = weak
|
||||||
.0
|
.0
|
||||||
.upgrade()
|
.upgrade()
|
||||||
|
@ -130,10 +136,18 @@ impl UserData for VoreLuaWeakStorage {
|
||||||
let mut this = strong
|
let mut this = strong
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
||||||
let key = lua.create_registry_value(args.1)?;
|
let key = lua.create_registry_value(args.2)?;
|
||||||
|
|
||||||
if let Some(old) = this.disk_presets.insert(args.0.to_str()?.to_string(), key) {
|
let new_preset = VoreLuaDiskPreset {
|
||||||
lua.remove_registry_value(old)?;
|
description: args.1.to_str()?.to_string(),
|
||||||
|
callback: key,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(old) = this
|
||||||
|
.disk_presets
|
||||||
|
.insert(args.0.to_str()?.to_string(), new_preset)
|
||||||
|
{
|
||||||
|
lua.remove_registry_value(old.callback)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
|
@ -187,7 +201,7 @@ impl UserData for VoreLuaWeakStorage {
|
||||||
.with_context(|| format!("Disk {} has no preset", index))
|
.with_context(|| format!("Disk {} has no preset", index))
|
||||||
.map_err(LuaError::external)?;
|
.map_err(LuaError::external)?;
|
||||||
|
|
||||||
let key = this
|
let preset = this
|
||||||
.disk_presets
|
.disk_presets
|
||||||
.get(&preset_name)
|
.get(&preset_name)
|
||||||
.clone()
|
.clone()
|
||||||
|
@ -196,7 +210,7 @@ impl UserData for VoreLuaWeakStorage {
|
||||||
})
|
})
|
||||||
.map_err(LuaError::external)?;
|
.map_err(LuaError::external)?;
|
||||||
|
|
||||||
lua.registry_value::<Function>(key)?
|
lua.registry_value::<Function>(&preset.callback)?
|
||||||
};
|
};
|
||||||
|
|
||||||
function.call((vm, instance, index, disk))
|
function.call((vm, instance, index, disk))
|
||||||
|
@ -262,6 +276,28 @@ impl QemuCommandBuilder {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_presets(self) -> anyhow::Result<Vec<(String, String)>> {
|
||||||
|
self.lua
|
||||||
|
.load(&self.script)
|
||||||
|
.eval::<()>()
|
||||||
|
.context("Failed to run the configured qemu lua script")?;
|
||||||
|
|
||||||
|
let result = {
|
||||||
|
self.storage
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.disk_presets
|
||||||
|
.iter()
|
||||||
|
.map(|(name, preset)| (name.clone(), preset.description.clone()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.clean_up()?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self, config: &InstanceConfig) -> Result<Vec<String>, anyhow::Error> {
|
pub fn build(self, config: &InstanceConfig) -> Result<Vec<String>, anyhow::Error> {
|
||||||
self.lua
|
self.lua
|
||||||
.load(&self.script)
|
.load(&self.script)
|
||||||
|
@ -287,6 +323,8 @@ impl QemuCommandBuilder {
|
||||||
|
|
||||||
let mut vm_instance = build_command.call::<MultiValue, VirtualMachine>(multi)?;
|
let mut vm_instance = build_command.call::<MultiValue, VirtualMachine>(multi)?;
|
||||||
|
|
||||||
|
mem::drop(build_command);
|
||||||
|
|
||||||
// Weird building way is for clarity sake
|
// Weird building way is for clarity sake
|
||||||
let mut cmd: Vec<String> = vec![
|
let mut cmd: Vec<String> = vec![
|
||||||
"-name".into(),
|
"-name".into(),
|
||||||
|
@ -318,6 +356,12 @@ impl QemuCommandBuilder {
|
||||||
|
|
||||||
cmd.append(&mut vm_instance.args);
|
cmd.append(&mut vm_instance.args);
|
||||||
|
|
||||||
|
self.clean_up()?;
|
||||||
|
|
||||||
|
Ok(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clean_up(self) -> anyhow::Result<()> {
|
||||||
self.lua.globals().raw_remove("vore")?;
|
self.lua.globals().raw_remove("vore")?;
|
||||||
|
|
||||||
self.lua.gc_collect()?;
|
self.lua.gc_collect()?;
|
||||||
|
@ -335,11 +379,11 @@ impl QemuCommandBuilder {
|
||||||
self.lua
|
self.lua
|
||||||
.remove_registry_value(storage.build_command.unwrap())?;
|
.remove_registry_value(storage.build_command.unwrap())?;
|
||||||
for (_, item) in storage.disk_presets.into_iter() {
|
for (_, item) in storage.disk_presets.into_iter() {
|
||||||
self.lua.remove_registry_value(item)?;
|
self.lua.remove_registry_value(item.callback)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lua.gc_collect()?;
|
self.lua.gc_collect()?;
|
||||||
|
|
||||||
Ok(cmd)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use paste::paste;
|
|
||||||
use crate::rpc::{Request, Response};
|
use crate::rpc::{Request, Response};
|
||||||
use crate::VirtualMachineInfo;
|
use crate::VirtualMachineInfo;
|
||||||
|
use paste::paste;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
macro_rules! define_requests {
|
macro_rules! define_requests {
|
||||||
($($name:ident($req:tt, $resp:tt))+) => {
|
($($name:ident($req:tt, $resp:tt))+) => {
|
||||||
|
@ -57,6 +57,12 @@ impl Response for AllResponses {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct DiskPreset {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
||||||
define_requests! {
|
define_requests! {
|
||||||
Info({}, {
|
Info({}, {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -102,4 +108,8 @@ define_requests! {
|
||||||
Kill({
|
Kill({
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}, {})
|
}, {})
|
||||||
}
|
|
||||||
|
DiskPresets({}, {
|
||||||
|
pub presets: Vec<DiskPreset>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
32
vore-core/src/utils.rs
Normal file
32
vore-core/src/utils.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
|
||||||
|
pub fn get_username_by_uid(uid: u32) -> anyhow::Result<Option<String>> {
|
||||||
|
unsafe {
|
||||||
|
let passwd = libc::getpwuid(uid);
|
||||||
|
if !passwd.is_null() {
|
||||||
|
return Ok(Some(
|
||||||
|
CStr::from_ptr((*passwd).pw_name)
|
||||||
|
.to_str()
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Username of user with uid {} is not valid UTF-8", uid)
|
||||||
|
})
|
||||||
|
.map(|x| x.to_string())?,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uid_by_username(username: &str) -> anyhow::Result<u32> {
|
||||||
|
unsafe {
|
||||||
|
let c_str = CString::new(username)?;
|
||||||
|
let passwd = libc::getpwnam(c_str.as_ptr());
|
||||||
|
if passwd.is_null() {
|
||||||
|
anyhow::bail!("No user found with the name {}", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((*passwd).pw_uid)
|
||||||
|
}
|
||||||
|
}
|
|
@ -467,8 +467,12 @@ impl VirtualMachine {
|
||||||
err?;
|
err?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.control_socket = None;
|
|
||||||
|
|
||||||
|
if let Some(mut proc) = self.process.take() {
|
||||||
|
let _ = proc.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.control_socket = None;
|
||||||
self.state = VirtualMachineState::Prepared;
|
self.state = VirtualMachineState::Prepared;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -8,7 +8,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.40"
|
anyhow = "1.0.40"
|
||||||
vore-core = { path = "../vore-core" }
|
vore-core = { features = ["client"], path = "../vore-core" }
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
pretty_env_logger = "0.3"
|
pretty_env_logger = "0.3"
|
||||||
clap = { version = "2.33.3", features = ["yaml"] }
|
clap = { version = "2.33.3", features = ["yaml"] }
|
|
@ -75,6 +75,12 @@ subcommands:
|
||||||
takes_value: true
|
takes_value: true
|
||||||
- list:
|
- list:
|
||||||
about: "List loaded VMs"
|
about: "List loaded VMs"
|
||||||
|
- disk:
|
||||||
|
setting: SubcommandRequiredElseHelp
|
||||||
|
about: "Disk related actions"
|
||||||
|
subcommands:
|
||||||
|
- presets:
|
||||||
|
about: "List the defined presets as currently known to the daemon"
|
||||||
|
|
||||||
- scream:
|
- scream:
|
||||||
setting: SubcommandRequiredElseHelp
|
setting: SubcommandRequiredElseHelp
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
use std::io::{BufRead, BufReader, Write};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use vore_core::rpc::{CommandCenter, Request};
|
|
||||||
use std::io::{BufReader, Write, BufRead};
|
|
||||||
use vore_core::{CloneableUnixStream, VirtualMachineInfo};
|
|
||||||
use vore_core::rpc::*;
|
use vore_core::rpc::*;
|
||||||
|
use vore_core::rpc::{CommandCenter, Request};
|
||||||
|
use vore_core::{CloneableUnixStream, VirtualMachineInfo};
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
stream: CloneableUnixStream,
|
stream: CloneableUnixStream,
|
||||||
|
@ -33,19 +33,30 @@ impl Client {
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_vm(&mut self, toml: &str, save: bool, cdroms: Vec<String>) -> anyhow::Result<VirtualMachineInfo> {
|
pub fn load_vm(
|
||||||
Ok(self.send(LoadRequest {
|
&mut self,
|
||||||
cdroms,
|
toml: &str,
|
||||||
save,
|
save: bool,
|
||||||
toml: toml.to_string(),
|
cdroms: Vec<String>,
|
||||||
working_directory: None,
|
) -> anyhow::Result<VirtualMachineInfo> {
|
||||||
})?.info)
|
Ok(self
|
||||||
|
.send(LoadRequest {
|
||||||
|
cdroms,
|
||||||
|
save,
|
||||||
|
toml: toml.to_string(),
|
||||||
|
working_directory: None,
|
||||||
|
})?
|
||||||
|
.info)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_vms(&mut self) -> anyhow::Result<Vec<VirtualMachineInfo>> {
|
pub fn list_vms(&mut self) -> anyhow::Result<Vec<VirtualMachineInfo>> {
|
||||||
Ok(self.send(ListRequest {})?.items)
|
Ok(self.send(ListRequest {})?.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_disk_presets(&mut self) -> anyhow::Result<Vec<DiskPreset>> {
|
||||||
|
Ok(self.send(DiskPresetsRequest {})?.presets)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn host_version(&mut self) -> anyhow::Result<InfoResponse> {
|
pub fn host_version(&mut self) -> anyhow::Result<InfoResponse> {
|
||||||
self.send(InfoRequest {})
|
self.send(InfoRequest {})
|
||||||
}
|
}
|
||||||
|
@ -64,4 +75,4 @@ impl Client {
|
||||||
self.send(StopRequest { name: vm })?;
|
self.send(StopRequest { name: vm })?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use std::os::unix::process::CommandExt;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::{fs, mem};
|
use std::{fs, mem};
|
||||||
use vore_core::consts::VORE_SOCKET;
|
use vore_core::consts::VORE_SOCKET;
|
||||||
|
use vore_core::rpc::DiskPreset;
|
||||||
use vore_core::{init_logging, VirtualMachineInfo};
|
use vore_core::{init_logging, VirtualMachineInfo};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -61,6 +62,16 @@ fn main_res() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
("disk", Some(args)) => match args.subcommand() {
|
||||||
|
("presets", _) => {
|
||||||
|
vore.list_presets()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
(s, _) => {
|
||||||
|
log::error!("Subcommand disk.{} not implemented", s);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
(s, _) => {
|
(s, _) => {
|
||||||
log::error!("Subcommand {} not implemented", s);
|
log::error!("Subcommand {} not implemented", s);
|
||||||
}
|
}
|
||||||
|
@ -140,6 +151,16 @@ impl VoreApp {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_presets(&mut self) -> anyhow::Result<()> {
|
||||||
|
let items = self.client.list_disk_presets()?;
|
||||||
|
|
||||||
|
for DiskPreset { name, description } in items {
|
||||||
|
println!("{}\t{}", name, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn prepare(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
|
fn prepare(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
let name = self.get_vm_name(args)?;
|
let name = self.get_vm_name(args)?;
|
||||||
self.client.prepare(
|
self.client.prepare(
|
||||||
|
|
|
@ -4,7 +4,6 @@ use signal_hook::consts::{SIGHUP, SIGINT, SIGTERM};
|
||||||
use signal_hook::iterator::{Handle, Signals, SignalsInfo};
|
use signal_hook::iterator::{Handle, Signals, SignalsInfo};
|
||||||
use signal_hook::low_level::signal_name;
|
use signal_hook::low_level::signal_name;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::{read_dir, read_to_string, DirEntry};
|
use std::fs::{read_dir, read_to_string, DirEntry};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
@ -16,8 +15,9 @@ use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{io, mem};
|
use std::{io, mem};
|
||||||
use vore_core::consts::{VORE_CONFIG, VORE_DIRECTORY, VORE_SOCKET};
|
use vore_core::consts::{VORE_CONFIG, VORE_DIRECTORY, VORE_SOCKET};
|
||||||
use vore_core::rpc::{AllRequests, AllResponses, Command, CommandCenter, Response};
|
use vore_core::rpc::{AllRequests, AllResponses, Command, CommandCenter, DiskPreset, Response};
|
||||||
use vore_core::{rpc, VirtualMachineInfo};
|
use vore_core::utils::get_username_by_uid;
|
||||||
|
use vore_core::{rpc, QemuCommandBuilder, VirtualMachineInfo};
|
||||||
use vore_core::{GlobalConfig, InstanceConfig, VirtualMachine};
|
use vore_core::{GlobalConfig, InstanceConfig, VirtualMachine};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -400,6 +400,19 @@ impl Daemon {
|
||||||
|
|
||||||
rpc::StartResponse {}.into_enum()
|
rpc::StartResponse {}.into_enum()
|
||||||
}
|
}
|
||||||
|
AllRequests::DiskPresets(_) => {
|
||||||
|
let builder =
|
||||||
|
QemuCommandBuilder::new(&self.global_config, PathBuf::from("/dev/empty"))?;
|
||||||
|
|
||||||
|
rpc::DiskPresetsResponse {
|
||||||
|
presets: builder
|
||||||
|
.list_presets()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, description)| DiskPreset { name, description })
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
.into_enum()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
|
@ -491,7 +504,6 @@ impl Daemon {
|
||||||
|
|
||||||
stream.set_nonblocking(true)?;
|
stream.set_nonblocking(true)?;
|
||||||
|
|
||||||
let mut user: Option<String> = None;
|
|
||||||
let ucred = unsafe {
|
let ucred = unsafe {
|
||||||
let mut ucred: libc::ucred = mem::zeroed();
|
let mut ucred: libc::ucred = mem::zeroed();
|
||||||
let mut length = size_of::<libc::ucred>() as u32;
|
let mut length = size_of::<libc::ucred>() as u32;
|
||||||
|
@ -502,17 +514,11 @@ impl Daemon {
|
||||||
(&mut ucred) as *mut _ as _,
|
(&mut ucred) as *mut _ as _,
|
||||||
&mut length,
|
&mut length,
|
||||||
);
|
);
|
||||||
let passwd = libc::getpwuid(ucred.uid);
|
|
||||||
if !passwd.is_null() {
|
|
||||||
user = CStr::from_ptr((*passwd).pw_name)
|
|
||||||
.to_str()
|
|
||||||
.ok()
|
|
||||||
.map(|x| x.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
ucred
|
ucred
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let user = get_username_by_uid(ucred.uid)?;
|
||||||
|
|
||||||
let conn = RpcConnection {
|
let conn = RpcConnection {
|
||||||
stream,
|
stream,
|
||||||
address,
|
address,
|
||||||
|
|
Loading…
Reference in a new issue