it works!!!
This commit is contained in:
parent
7411bb5972
commit
b5c4d38b77
12 changed files with 265 additions and 82 deletions
|
@ -1,7 +1,13 @@
|
||||||
[machine]
|
[machine]
|
||||||
name = "win10"
|
name = "win10"
|
||||||
memory = "12G"
|
memory = "12G"
|
||||||
features = ["uefi", "spice", "scream"]
|
features = [
|
||||||
|
"uefi",
|
||||||
|
"spice",
|
||||||
|
"pulse",
|
||||||
|
# "scream",
|
||||||
|
"looking-glass"
|
||||||
|
]
|
||||||
|
|
||||||
[cpu]
|
[cpu]
|
||||||
amount = 12
|
amount = 12
|
||||||
|
@ -18,23 +24,23 @@ path = "/dev/disk/by-id/wwn-0x500a0751f008e09d"
|
||||||
preset = "ssd"
|
preset = "ssd"
|
||||||
path = "/dev/disk/by-id/wwn-0x5002538e4038852d"
|
path = "/dev/disk/by-id/wwn-0x5002538e4038852d"
|
||||||
|
|
||||||
#[[vfio]]
|
[[vfio]]
|
||||||
#vendor = 0x10de
|
vendor = 0x10de
|
||||||
#device = 0x1b80
|
device = 0x1b80
|
||||||
#index = 1
|
index = 1
|
||||||
#
|
|
||||||
#graphics = true
|
graphics = true
|
||||||
#
|
|
||||||
#[[vfio]]
|
[[vfio]]
|
||||||
#vendor = 0x10de
|
vendor = 0x10de
|
||||||
#device = 0x10f0
|
device = 0x10f0
|
||||||
#index = 1
|
index = 1
|
||||||
#
|
|
||||||
#[[vfio]]
|
[[vfio]]
|
||||||
#vendor = 0x1022
|
vendor = 0x1022
|
||||||
#device = 0x149c
|
device = 0x149c
|
||||||
#addr = "0b:00.3"
|
addr = "0b:00.3"
|
||||||
#
|
|
||||||
#[looking-glass]
|
[looking-glass]
|
||||||
#width = 2560
|
width = 2560
|
||||||
#height = 1080
|
height = 1080
|
||||||
|
|
|
@ -125,14 +125,20 @@ 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.pulse.enabled then
|
||||||
|
vm:arg("-device", "intel-hda", "-device", "hda-duplex")
|
||||||
|
vm:arg("-audiodev", "pa,server=/run/user/1000/pulse/native,id=pa0")
|
||||||
|
end
|
||||||
|
|
||||||
vm:arg(
|
vm:arg(
|
||||||
"-machine",
|
"-machine",
|
||||||
"q35,accel=kvm,usb=off,vmport=off,dump-guest-core=off,kernel_irqchip=on"
|
"q35,accel=kvm,usb=off,vmport=off,dump-guest-core=off,kernel_irqchip=on"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- Pls update
|
||||||
vm:arg(
|
vm:arg(
|
||||||
"-cpu",
|
"-cpu",
|
||||||
"host,migratable=on,hv-time,hv-relaxed,hv-vapic,hv-spinlocks=0x1fff,hv-vendor-id=whatever,kvm=off"
|
"host,hv-time,hv-relaxed,hv-vapic,hv-spinlocks=0x1fff,hv-vendor-id=whatever,kvm=off,+topoext"
|
||||||
)
|
)
|
||||||
|
|
||||||
return vm
|
return vm
|
||||||
|
|
|
@ -83,6 +83,9 @@ end
|
||||||
---@field enabled boolean
|
---@field enabled boolean
|
||||||
---@field socket_path string
|
---@field socket_path string
|
||||||
|
|
||||||
|
---@class Pulse
|
||||||
|
---@field enabled boolean
|
||||||
|
|
||||||
---@class Instance
|
---@class Instance
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field kvm boolean
|
---@field kvm boolean
|
||||||
|
@ -96,6 +99,7 @@ end
|
||||||
---@field looking_glass LookingGlass
|
---@field looking_glass LookingGlass
|
||||||
---@field scream Scream
|
---@field scream Scream
|
||||||
---@field spice Spice
|
---@field spice Spice
|
||||||
|
---@field pulse Pulse
|
||||||
|
|
||||||
----
|
----
|
||||||
---Add a disk definition to the argument list
|
---Add a disk definition to the argument list
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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};
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
@ -19,6 +19,7 @@ pub struct InstanceConfig {
|
||||||
pub vfio: Vec<VfioConfig>,
|
pub vfio: Vec<VfioConfig>,
|
||||||
pub looking_glass: LookingGlassConfig,
|
pub looking_glass: LookingGlassConfig,
|
||||||
pub scream: ScreamConfig,
|
pub scream: ScreamConfig,
|
||||||
|
pub pulse: PulseConfig,
|
||||||
pub spice: SpiceConfig,
|
pub spice: SpiceConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,15 +76,15 @@ impl InstanceConfig {
|
||||||
|
|
||||||
instance_config.looking_glass = LookingGlassConfig::from_table(
|
instance_config.looking_glass = LookingGlassConfig::from_table(
|
||||||
config.get_table("looking-glass").unwrap_or_default(),
|
config.get_table("looking-glass").unwrap_or_default(),
|
||||||
&instance_config.name,
|
|
||||||
)?;
|
)?;
|
||||||
instance_config.scream = ScreamConfig::from_table(
|
instance_config.scream = ScreamConfig::from_table(
|
||||||
config.get_table("scream").unwrap_or_default(),
|
config.get_table("scream").unwrap_or_default(),
|
||||||
&instance_config.name,
|
|
||||||
)?;
|
)?;
|
||||||
instance_config.spice =
|
instance_config.spice =
|
||||||
SpiceConfig::from_table(config.get_table("spice").unwrap_or_default())?;
|
SpiceConfig::from_table(config.get_table("spice").unwrap_or_default())?;
|
||||||
|
|
||||||
|
instance_config.pulse = PulseConfig::from_table(config.get_table("pulse").unwrap_or_default())?;
|
||||||
|
|
||||||
if let Ok(features) = config.get::<Vec<String>>("machine.features") {
|
if let Ok(features) = config.get::<Vec<String>>("machine.features") {
|
||||||
for feature in features {
|
for feature in features {
|
||||||
match feature.as_str() {
|
match feature.as_str() {
|
||||||
|
@ -91,6 +92,7 @@ impl InstanceConfig {
|
||||||
"spice" => instance_config.spice.enabled = true,
|
"spice" => instance_config.spice.enabled = true,
|
||||||
"scream" => instance_config.scream.enabled = true,
|
"scream" => instance_config.scream.enabled = true,
|
||||||
"uefi" => instance_config.uefi.enabled = true,
|
"uefi" => instance_config.uefi.enabled = true,
|
||||||
|
"pulse" => instance_config.pulse.enabled = true,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +117,7 @@ impl Default for InstanceConfig {
|
||||||
vfio: vec![],
|
vfio: vec![],
|
||||||
looking_glass: Default::default(),
|
looking_glass: Default::default(),
|
||||||
scream: Default::default(),
|
scream: Default::default(),
|
||||||
|
pulse: Default::default(),
|
||||||
spice: Default::default(),
|
spice: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,7 +288,6 @@ pub struct ScreamConfig {
|
||||||
impl ScreamConfig {
|
impl ScreamConfig {
|
||||||
pub fn from_table(
|
pub fn from_table(
|
||||||
table: HashMap<String, Value>,
|
table: HashMap<String, Value>,
|
||||||
name: &str,
|
|
||||||
) -> Result<ScreamConfig, anyhow::Error> {
|
) -> Result<ScreamConfig, anyhow::Error> {
|
||||||
let mut cfg = ScreamConfig::default();
|
let mut cfg = ScreamConfig::default();
|
||||||
if let Some(enabled) = table.get("enabled").cloned() {
|
if let Some(enabled) = table.get("enabled").cloned() {
|
||||||
|
@ -294,8 +296,6 @@ impl ScreamConfig {
|
||||||
|
|
||||||
if let Some(mem_path) = table.get("mem-path").cloned() {
|
if let Some(mem_path) = table.get("mem-path").cloned() {
|
||||||
cfg.mem_path = mem_path.into_str()?;
|
cfg.mem_path = mem_path.into_str()?;
|
||||||
} else {
|
|
||||||
cfg.mem_path = format!("/dev/shm/{}-scream", name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(buffer_size) = table.get("buffer-size").cloned() {
|
if let Some(buffer_size) = table.get("buffer-size").cloned() {
|
||||||
|
@ -370,7 +370,6 @@ impl LookingGlassConfig {
|
||||||
|
|
||||||
pub fn from_table(
|
pub fn from_table(
|
||||||
table: HashMap<String, Value>,
|
table: HashMap<String, Value>,
|
||||||
name: &str,
|
|
||||||
) -> Result<LookingGlassConfig, anyhow::Error> {
|
) -> Result<LookingGlassConfig, anyhow::Error> {
|
||||||
let mut cfg = LookingGlassConfig::default();
|
let mut cfg = LookingGlassConfig::default();
|
||||||
|
|
||||||
|
@ -380,8 +379,6 @@ impl LookingGlassConfig {
|
||||||
|
|
||||||
if let Some(mem_path) = table.get("mem-path").cloned() {
|
if let Some(mem_path) = table.get("mem-path").cloned() {
|
||||||
cfg.mem_path = mem_path.into_str()?;
|
cfg.mem_path = mem_path.into_str()?;
|
||||||
} else {
|
|
||||||
cfg.mem_path = format!("/dev/shm/{}/looking-glass", name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()) {
|
||||||
|
@ -613,6 +610,26 @@ impl VfioConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
|
pub struct PulseConfig {
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PulseConfig {
|
||||||
|
pub fn from_table(table: HashMap<String, Value>) -> Result<PulseConfig, anyhow::Error> {
|
||||||
|
let mut cfg = PulseConfig {
|
||||||
|
enabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(enabled) = table.get("enabled").cloned() {
|
||||||
|
cfg.enabled = enabled.into_bool()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
pub struct SpiceConfig {
|
pub struct SpiceConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
@ -623,13 +640,17 @@ impl SpiceConfig {
|
||||||
pub fn from_table(table: HashMap<String, Value>) -> Result<SpiceConfig, anyhow::Error> {
|
pub fn from_table(table: HashMap<String, Value>) -> Result<SpiceConfig, anyhow::Error> {
|
||||||
let mut cfg = SpiceConfig {
|
let mut cfg = SpiceConfig {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
socket_path: "/tmp/win10.sock".to_string(),
|
socket_path: "".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
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()?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -655,6 +676,11 @@ impl<'de> Deserialize<'de> for PCIAddress {
|
||||||
formatter.write_str("Expecting a string")
|
formatter.write_str("Expecting a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
|
||||||
|
E: de::Error, {
|
||||||
|
Ok(v.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
|
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,11 +223,11 @@ impl QemuCommandBuilder {
|
||||||
global: &GlobalConfig,
|
global: &GlobalConfig,
|
||||||
working_dir: PathBuf,
|
working_dir: PathBuf,
|
||||||
) -> Result<QemuCommandBuilder, anyhow::Error> {
|
) -> Result<QemuCommandBuilder, anyhow::Error> {
|
||||||
let lua = Path::new(GLOBAL_CONFIG_LOCATION).join(&global.qemu.script);
|
let lua = Path::new(GLOBAL_CONFIG_LOCATION).parent().unwrap().join(&global.qemu.script);
|
||||||
|
|
||||||
let builder = QemuCommandBuilder {
|
let builder = QemuCommandBuilder {
|
||||||
lua: Lua::new(),
|
lua: Lua::new(),
|
||||||
script: fs::read_to_string(lua)?,
|
script: fs::read_to_string(&lua).with_context(|| format!("Failed to load lua qemu command build script ({:?})", lua))?,
|
||||||
storage: VoreLuaStorage::new(working_dir),
|
storage: VoreLuaStorage::new(working_dir),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ macro_rules! define_requests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(untagged, rename_all = "snake_case")]
|
#[serde(tag = "answer", rename_all = "snake_case")]
|
||||||
pub enum AllResponses {
|
pub enum AllResponses {
|
||||||
$($name(paste! { [<$name Response >] })),+
|
$($name(paste! { [<$name Response >] })),+
|
||||||
}
|
}
|
||||||
|
@ -98,4 +98,8 @@ define_requests! {
|
||||||
Unload({
|
Unload({
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
|
Kill({
|
||||||
|
pub name: String,
|
||||||
|
}, {})
|
||||||
}
|
}
|
|
@ -43,6 +43,7 @@ impl CommandCenter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_answer<R: Request>(answer: &str) -> Result<(u64, R::Response), CommandError> {
|
pub fn read_answer<R: Request>(answer: &str) -> Result<(u64, R::Response), CommandError> {
|
||||||
|
log::debug!("Reading answer: {}", answer);
|
||||||
let answer_obj: Answer<R::Response> = serde_json::from_str(answer).map_err(|err| CommandError::InternalError(err.into()))?;
|
let answer_obj: Answer<R::Response> = serde_json::from_str(answer).map_err(|err| CommandError::InternalError(err.into()))?;
|
||||||
|
|
||||||
match answer_obj.data {
|
match answer_obj.data {
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub struct Answer<R: Response> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(tag = "status", rename_all = "snake_case")]
|
||||||
pub enum AnswerResult<R: Response> {
|
pub enum AnswerResult<R: Response> {
|
||||||
Error(AnswerError),
|
Error(AnswerError),
|
||||||
#[serde(bound = "R: Response")]
|
#[serde(bound = "R: Response")]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{GlobalConfig, InstanceConfig, QemuCommandBuilder};
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
use beau_collector::BeauCollector;
|
use beau_collector::BeauCollector;
|
||||||
use qapi::qmp::{QMP, Event};
|
use qapi::qmp::{QMP, Event};
|
||||||
use qapi::{Qmp, ExecuteError};
|
use qapi::{Qmp};
|
||||||
use std::{fmt, mem};
|
use std::{fmt, mem};
|
||||||
use std::fmt::{Debug, Formatter, Display};
|
use std::fmt::{Debug, Formatter, Display};
|
||||||
use std::fs::{read_link, OpenOptions, read_dir};
|
use std::fs::{read_link, OpenOptions, read_dir};
|
||||||
|
@ -76,7 +76,7 @@ impl Debug for ControlSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AUTO_UNBIND_BLACKLIST: &[&str] = &["nvidia"];
|
const AUTO_UNBIND_BLACKLIST: &[&str] = &["nvidia", "amdgpu"];
|
||||||
|
|
||||||
impl VirtualMachine {
|
impl VirtualMachine {
|
||||||
pub fn new<P: AsRef<Path>>(
|
pub fn new<P: AsRef<Path>>(
|
||||||
|
@ -111,6 +111,8 @@ impl VirtualMachine {
|
||||||
let mut results = vec![];
|
let mut results = vec![];
|
||||||
results.extend(self.prepare_disks());
|
results.extend(self.prepare_disks());
|
||||||
results.extend(self.prepare_vfio(execute_fixes, force));
|
results.extend(self.prepare_vfio(execute_fixes, force));
|
||||||
|
results.extend(self.prepare_shm());
|
||||||
|
results.extend(self.prepare_sockets());
|
||||||
results
|
results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.bcollect::<()>()
|
.bcollect::<()>()
|
||||||
|
@ -122,6 +124,52 @@ impl VirtualMachine {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prepare_shm(&mut self) -> Vec<Result<(), anyhow::Error>> {
|
||||||
|
let mut shm = vec![];
|
||||||
|
if self.config.looking_glass.enabled {
|
||||||
|
if self.config.looking_glass.mem_path.is_empty() {
|
||||||
|
self.config.looking_glass.mem_path = format!("/dev/shm/vore/{}/looking-glass", self.config.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
shm.push(&self.config.looking_glass.mem_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.config.scream.enabled {
|
||||||
|
if self.config.scream.mem_path.is_empty() {
|
||||||
|
self.config.scream.mem_path = format!("/dev/shm/vore/{}/scream", self.config.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
shm.push(&self.config.scream.mem_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
shm
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| Path::new(x))
|
||||||
|
.filter_map(|x| x.parent())
|
||||||
|
.filter(|x| !x.is_dir())
|
||||||
|
.map(|x| std::fs::create_dir_all(&x).with_context(|| format!("Failed creating directories for shared memory ({:?})", x)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_sockets(&mut self) -> Vec<Result<(), anyhow::Error>> {
|
||||||
|
let mut sockets = vec![];
|
||||||
|
if self.config.spice.enabled {
|
||||||
|
if self.config.spice.socket_path.is_empty() {
|
||||||
|
self.config.spice.socket_path = self.working_dir.join("spice.sock").to_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
sockets.push(&self.config.spice.socket_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
sockets
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| Path::new(x))
|
||||||
|
.filter_map(|x| x.parent())
|
||||||
|
.filter(|x| !x.is_dir())
|
||||||
|
.map(|x| std::fs::create_dir_all(&x).with_context(|| format!("Failed creating directories for shared memory ({:?})", x)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Doesn't really prepare them, but mostly checks if the user has permissions to read them
|
/// Doesn't really prepare them, but mostly checks if the user has permissions to read them
|
||||||
///
|
///
|
||||||
|
@ -305,6 +353,14 @@ impl VirtualMachine {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn boop(&mut self) -> Result<(), anyhow::Error> {
|
||||||
|
if let Some(qmp) = self.control_socket.as_mut() {
|
||||||
|
qmp.qmp.nop()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.process_qmp_events()
|
||||||
|
}
|
||||||
|
|
||||||
fn process_qmp_events(&mut self) -> Result<(), anyhow::Error> {
|
fn process_qmp_events(&mut self) -> Result<(), anyhow::Error> {
|
||||||
let events = if let Some(qmp) = self.control_socket.as_mut() {
|
let events = if let Some(qmp) = self.control_socket.as_mut() {
|
||||||
// While we could iter, we keep hold of the mutable reference, so it's easier to just collect the events
|
// While we could iter, we keep hold of the mutable reference, so it's easier to just collect the events
|
||||||
|
@ -366,18 +422,6 @@ impl VirtualMachine {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop_now(&mut self) -> Result<(), anyhow::Error> {
|
|
||||||
self.stop()?;
|
|
||||||
|
|
||||||
if let Some(mut process) = self.process.take() {
|
|
||||||
self.wait(Some(Duration::from_secs(30)), VirtualMachineState::Stopped)?;
|
|
||||||
self.quit()?;
|
|
||||||
process.wait()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_till_stopped(&mut self) -> Result<(), anyhow::Error> {
|
pub fn wait_till_stopped(&mut self) -> Result<(), anyhow::Error> {
|
||||||
self.wait(None, VirtualMachineState::Stopped)?;
|
self.wait(None, VirtualMachineState::Stopped)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -388,19 +432,12 @@ impl VirtualMachine {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.send_qmp_command(&qapi_qmp::quit {})
|
match self.send_qmp_command(&qapi_qmp::quit {}) {
|
||||||
.map(|_| ())
|
Err(err) if err.downcast_ref::<io::Error>().map_or(false, |x| x.kind() == io::ErrorKind::UnexpectedEof) => {}
|
||||||
.or_else(|x|
|
err => { err?; }
|
||||||
if let Some(ExecuteError::Io(err)) = x.downcast_ref::<ExecuteError>() {
|
}
|
||||||
if err.kind() == ErrorKind::UnexpectedEof {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
|
||||||
Err(x)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(x)
|
|
||||||
})
|
|
||||||
.map_err(From::from)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait(&mut self, duration: Option<Duration>, target_state: VirtualMachineState) -> Result<bool, anyhow::Error> {
|
fn wait(&mut self, duration: Option<Duration>, target_state: VirtualMachineState) -> Result<bool, anyhow::Error> {
|
||||||
|
@ -438,8 +475,12 @@ impl VirtualMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.state == VirtualMachineState::Loaded {
|
||||||
|
self.prepare(true, false)?
|
||||||
|
}
|
||||||
|
|
||||||
let mut command = Command::new("qemu-system-x86_64");
|
let mut command = Command::new("qemu-system-x86_64");
|
||||||
command.args(self.get_cmd_line()?);
|
command.args(self.get_cmd_line().context("Failed to generate qemu command line")?);
|
||||||
self.process = Some(command.spawn()?);
|
self.process = Some(command.spawn()?);
|
||||||
|
|
||||||
let mut res = || {
|
let mut res = || {
|
||||||
|
@ -477,7 +518,7 @@ impl VirtualMachine {
|
||||||
_info: handshake,
|
_info: handshake,
|
||||||
};
|
};
|
||||||
|
|
||||||
// self.pin_qemu_threads()?;
|
self.pin_qemu_threads()?;
|
||||||
|
|
||||||
control_socket
|
control_socket
|
||||||
.qmp
|
.qmp
|
||||||
|
@ -495,7 +536,7 @@ impl VirtualMachine {
|
||||||
let result_ = res();
|
let result_ = res();
|
||||||
if result_.is_err() {
|
if result_.is_err() {
|
||||||
if let Some(mut qemu) = self.process.take() {
|
if let Some(mut qemu) = self.process.take() {
|
||||||
qemu.kill()?;
|
let _ = qemu.kill();
|
||||||
qemu.wait()?;
|
qemu.wait()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -547,4 +588,4 @@ impl Write for CloneableUnixStream {
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
self.lock()?.flush()
|
self.lock()?.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -54,4 +54,14 @@ impl Client {
|
||||||
self.send(PrepareRequest { name: vm, cdroms })?;
|
self.send(PrepareRequest { name: vm, cdroms })?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self, vm: String, cdroms: Vec<String>) -> anyhow::Result<()> {
|
||||||
|
self.send(StartRequest { name: vm, cdroms })?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self, vm: String) -> anyhow::Result<()> {
|
||||||
|
self.send(StopRequest { name: vm })?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
mod client;
|
mod client;
|
||||||
|
|
||||||
use vore_core::{init_logging};
|
use vore_core::{init_logging, VirtualMachineInfo};
|
||||||
use crate::client::Client;
|
use crate::client::Client;
|
||||||
use clap::{App, ArgMatches};
|
use clap::{App, ArgMatches};
|
||||||
use std::fs;
|
use std::{fs, mem};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use std::option::Option::Some;
|
use std::option::Option::Some;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
init_logging();
|
init_logging();
|
||||||
|
@ -38,6 +40,18 @@ fn main_res() -> anyhow::Result<()> {
|
||||||
vore.prepare(args)?;
|
vore.prepare(args)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
("start", Some(args)) => {
|
||||||
|
vore.start(args)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
("stop", Some(args)) => {
|
||||||
|
vore.stop(args)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
("looking-glass", Some(args)) => {
|
||||||
|
vore.looking_glass(args)?;
|
||||||
|
}
|
||||||
|
|
||||||
("daemon", Some(args)) => {
|
("daemon", Some(args)) => {
|
||||||
match args.subcommand() {
|
match args.subcommand() {
|
||||||
("version", _) => {
|
("version", _) => {
|
||||||
|
@ -60,7 +74,7 @@ fn main_res() -> anyhow::Result<()> {
|
||||||
|
|
||||||
struct LoadVMOptions {
|
struct LoadVMOptions {
|
||||||
config: String,
|
config: String,
|
||||||
cdroms: Vec<String>,
|
cd_roms: Vec<String>,
|
||||||
save: bool,
|
save: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +85,7 @@ fn get_load_vm_options(args: &ArgMatches) -> anyhow::Result<LoadVMOptions> {
|
||||||
|
|
||||||
Ok(LoadVMOptions {
|
Ok(LoadVMOptions {
|
||||||
config,
|
config,
|
||||||
cdroms: args.values_of("cdrom").map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()),
|
cd_roms: args.values_of("cdrom").map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()),
|
||||||
save: args.is_present("save"),
|
save: args.is_present("save"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -82,12 +96,17 @@ struct VoreApp {
|
||||||
|
|
||||||
impl VoreApp {
|
impl VoreApp {
|
||||||
fn get_vm_name(&mut self, args: &ArgMatches) -> anyhow::Result<String> {
|
fn get_vm_name(&mut self, args: &ArgMatches) -> anyhow::Result<String> {
|
||||||
|
self.get_vm(args).map(|x| x.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_vm(&mut self, args: &ArgMatches) -> anyhow::Result<VirtualMachineInfo> {
|
||||||
|
let mut items = self.client.list_vms()?;
|
||||||
if let Some(vm_name) = args.value_of("vm-name") {
|
if let Some(vm_name) = args.value_of("vm-name") {
|
||||||
Ok(vm_name.to_string())
|
items.into_iter().find(|x| x.name == vm_name)
|
||||||
|
.with_context(|| format!("Couldn't find VM with the name '{}'", vm_name))
|
||||||
} else {
|
} else {
|
||||||
let mut items = self.client.list_vms()?;
|
|
||||||
match (items.len(), items.pop()) {
|
match (items.len(), items.pop()) {
|
||||||
(amount, Some(x)) if amount == 1 => return Ok(x.name),
|
(amount, Some(x)) if amount == 1 => return Ok(x),
|
||||||
(0, None) => anyhow::bail!("There are no VM's loaded"),
|
(0, None) => anyhow::bail!("There are no VM's loaded"),
|
||||||
_ => anyhow::bail!("Multiple VM's are loaded, please specify one"),
|
_ => anyhow::bail!("Multiple VM's are loaded, please specify one"),
|
||||||
}
|
}
|
||||||
|
@ -103,7 +122,7 @@ impl VoreApp {
|
||||||
fn load(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
|
fn load(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
let vm_options = get_load_vm_options(args)?;
|
let vm_options = get_load_vm_options(args)?;
|
||||||
|
|
||||||
let vm_info = self.client.load_vm(&vm_options.config, vm_options.save, vm_options.cdroms)?;
|
let vm_info = self.client.load_vm(&vm_options.config, vm_options.save, vm_options.cd_roms)?;
|
||||||
log::info!("Loaded VM {}", vm_info.name);
|
log::info!("Loaded VM {}", vm_info.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -123,4 +142,38 @@ impl VoreApp {
|
||||||
self.client.prepare(name, args.values_of("cdrom").map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()))?;
|
self.client.prepare(name, args.values_of("cdrom").map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
|
let name = self.get_vm_name(args)?;
|
||||||
|
self.client.start(name, args.values_of("cdrom").map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn looking_glass(mut self, args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
|
let vm = self.get_vm(args)?;
|
||||||
|
if !vm.config.looking_glass.enabled {
|
||||||
|
anyhow::bail!("VM '{}' has no looking glass", vm.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut command = Command::new(std::env::var("LOOKING_GLASS").unwrap_or("looking-glass-client".to_string()));
|
||||||
|
if vm.config.spice.enabled {
|
||||||
|
command.args(&["-c", &vm.config.spice.socket_path, "-p", "0"]);
|
||||||
|
} else {
|
||||||
|
command.args(&["-s", "no"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
command.args(&["-f", &vm.config.looking_glass.mem_path]);
|
||||||
|
command.args(args.values_of("looking-glass-args").map_or(vec![], |x| x.into_iter().collect::<Vec<_>>()));
|
||||||
|
|
||||||
|
mem::drop(self);
|
||||||
|
command.exec();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
|
let name = self.get_vm_name(args)?;
|
||||||
|
self.client.stop(name)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ impl RPCConnection {
|
||||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
enum EventTarget {
|
enum EventTarget {
|
||||||
RPCListener,
|
RPCListener,
|
||||||
_Machine(String),
|
Machine(String),
|
||||||
RPCConnection(usize),
|
RPCConnection(usize),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
@ -236,15 +236,43 @@ impl Daemon {
|
||||||
|
|
||||||
rpc::PrepareResponse {}.into_enum()
|
rpc::PrepareResponse {}.into_enum()
|
||||||
}
|
}
|
||||||
AllRequests::Start(_) => {
|
AllRequests::Start(val) => {
|
||||||
anyhow::bail!("Unimplemented");
|
let cloned = if let Some(machine) = self.machines.get_mut(&val.name) {
|
||||||
|
machine.start()?;
|
||||||
|
|
||||||
|
machine.control_stream().cloned()
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("No machine with the name {} exists", val.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cloned) = cloned {
|
||||||
|
let new_id = self.add_target(EventTarget::Machine(val.name.clone()));
|
||||||
|
self.poller.add(&cloned, Event::readable(new_id))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc::StartResponse {}.into_enum()
|
||||||
}
|
}
|
||||||
AllRequests::Stop(_) => {
|
AllRequests::Stop(val) => {
|
||||||
anyhow::bail!("Unimplemented");
|
if let Some(machine) = self.machines.get_mut(&val.name) {
|
||||||
|
machine.stop()?;
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("No machine with the name {} exists", val.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc::StartResponse {}.into_enum()
|
||||||
}
|
}
|
||||||
AllRequests::Unload(_) => {
|
AllRequests::Unload(_) => {
|
||||||
anyhow::bail!("Unimplemented");
|
anyhow::bail!("Unimplemented");
|
||||||
}
|
}
|
||||||
|
AllRequests::Kill(val) => {
|
||||||
|
if let Some(machine) = self.machines.get_mut(&val.name) {
|
||||||
|
machine.quit()?;
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("No machine with the name {} exists", val.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc::StartResponse {}.into_enum()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
|
@ -273,8 +301,12 @@ impl Daemon {
|
||||||
self.poller.modify(&self.rpc_listener, Event::readable(event.key))?;
|
self.poller.modify(&self.rpc_listener, Event::readable(event.key))?;
|
||||||
self.accept_rpc_connections()?;
|
self.accept_rpc_connections()?;
|
||||||
}
|
}
|
||||||
EventTarget::_Machine(name) if self.machines.contains_key(&name) => {
|
EventTarget::Machine(name) if self.machines.contains_key(&name) => {
|
||||||
if let Some(control_socket) = self.machines[&name].control_stream() {
|
if let Some(machine) = self.machines.get_mut(&name) {
|
||||||
|
machine.boop()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(control_socket) = self.machines.get(&name).and_then(|x| x.control_stream()) {
|
||||||
self.poller.modify(control_socket, Event::readable(event.key))?;
|
self.poller.modify(control_socket, Event::readable(event.key))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue