it works!!!

main
eater 3 years ago
parent 7411bb5972
commit b5c4d38b77
Signed by: eater
GPG Key ID: AD2560A0F84F0759

@ -1,7 +1,13 @@
[machine]
name = "win10"
memory = "12G"
features = ["uefi", "spice", "scream"]
features = [
"uefi",
"spice",
"pulse",
# "scream",
"looking-glass"
]
[cpu]
amount = 12
@ -18,23 +24,23 @@ path = "/dev/disk/by-id/wwn-0x500a0751f008e09d"
preset = "ssd"
path = "/dev/disk/by-id/wwn-0x5002538e4038852d"
#[[vfio]]
#vendor = 0x10de
#device = 0x1b80
#index = 1
#
#graphics = true
#
#[[vfio]]
#vendor = 0x10de
#device = 0x10f0
#index = 1
#
#[[vfio]]
#vendor = 0x1022
#device = 0x149c
#addr = "0b:00.3"
#
#[looking-glass]
#width = 2560
#height = 1080
[[vfio]]
vendor = 0x10de
device = 0x1b80
index = 1
graphics = true
[[vfio]]
vendor = 0x10de
device = 0x10f0
index = 1
[[vfio]]
vendor = 0x1022
device = 0x149c
addr = "0b:00.3"
[looking-glass]
width = 2560
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")
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(
"-machine",
"q35,accel=kvm,usb=off,vmport=off,dump-guest-core=off,kernel_irqchip=on"
)
-- Pls update
vm:arg(
"-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

@ -83,6 +83,9 @@ end
---@field enabled boolean
---@field socket_path string
---@class Pulse
---@field enabled boolean
---@class Instance
---@field name string
---@field kvm boolean
@ -96,6 +99,7 @@ end
---@field looking_glass LookingGlass
---@field scream Scream
---@field spice Spice
---@field pulse Pulse
----
---Add a disk definition to the argument list

@ -1,6 +1,6 @@
use anyhow::{Context, Error};
use config::{Config, File, FileFormat, Value};
use serde::de::Visitor;
use serde::de::{Visitor};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
@ -19,6 +19,7 @@ pub struct InstanceConfig {
pub vfio: Vec<VfioConfig>,
pub looking_glass: LookingGlassConfig,
pub scream: ScreamConfig,
pub pulse: PulseConfig,
pub spice: SpiceConfig,
}
@ -75,15 +76,15 @@ impl InstanceConfig {
instance_config.looking_glass = LookingGlassConfig::from_table(
config.get_table("looking-glass").unwrap_or_default(),
&instance_config.name,
)?;
instance_config.scream = ScreamConfig::from_table(
config.get_table("scream").unwrap_or_default(),
&instance_config.name,
)?;
instance_config.spice =
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") {
for feature in features {
match feature.as_str() {
@ -91,6 +92,7 @@ impl InstanceConfig {
"spice" => instance_config.spice.enabled = true,
"scream" => instance_config.scream.enabled = true,
"uefi" => instance_config.uefi.enabled = true,
"pulse" => instance_config.pulse.enabled = true,
_ => {}
}
}
@ -115,6 +117,7 @@ impl Default for InstanceConfig {
vfio: vec![],
looking_glass: Default::default(),
scream: Default::default(),
pulse: Default::default(),
spice: Default::default(),
}
}
@ -285,7 +288,6 @@ pub struct ScreamConfig {
impl ScreamConfig {
pub fn from_table(
table: HashMap<String, Value>,
name: &str,
) -> Result<ScreamConfig, anyhow::Error> {
let mut cfg = ScreamConfig::default();
if let Some(enabled) = table.get("enabled").cloned() {
@ -294,8 +296,6 @@ impl ScreamConfig {
if let Some(mem_path) = table.get("mem-path").cloned() {
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() {
@ -370,7 +370,6 @@ impl LookingGlassConfig {
pub fn from_table(
table: HashMap<String, Value>,
name: &str,
) -> Result<LookingGlassConfig, anyhow::Error> {
let mut cfg = LookingGlassConfig::default();
@ -380,8 +379,6 @@ impl LookingGlassConfig {
if let Some(mem_path) = table.get("mem-path").cloned() {
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()) {
@ -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)]
pub struct SpiceConfig {
pub enabled: bool,
@ -623,13 +640,17 @@ impl SpiceConfig {
pub fn from_table(table: HashMap<String, Value>) -> Result<SpiceConfig, anyhow::Error> {
let mut cfg = SpiceConfig {
enabled: false,
socket_path: "/tmp/win10.sock".to_string(),
socket_path: "".to_string(),
};
if let Some(enabled) = table.get("enabled").cloned() {
cfg.enabled = enabled.into_bool()?;
}
if let Some(socket_path) = table.get("socket-path").cloned() {
cfg.socket_path = socket_path.into_str()?;
}
Ok(cfg)
}
}
@ -655,6 +676,11 @@ impl<'de> Deserialize<'de> for PCIAddress {
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> {
Ok(v)
}

@ -223,11 +223,11 @@ impl QemuCommandBuilder {
global: &GlobalConfig,
working_dir: PathBuf,
) -> 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 {
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),
};

@ -13,7 +13,7 @@ macro_rules! define_requests {
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged, rename_all = "snake_case")]
#[serde(tag = "answer", rename_all = "snake_case")]
pub enum AllResponses {
$($name(paste! { [<$name Response >] })),+
}
@ -98,4 +98,8 @@ define_requests! {
Unload({
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> {
log::debug!("Reading answer: {}", answer);
let answer_obj: Answer<R::Response> = serde_json::from_str(answer).map_err(|err| CommandError::InternalError(err.into()))?;
match answer_obj.data {

@ -18,7 +18,7 @@ pub struct Answer<R: Response> {
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum AnswerResult<R: Response> {
Error(AnswerError),
#[serde(bound = "R: Response")]

@ -2,7 +2,7 @@ use crate::{GlobalConfig, InstanceConfig, QemuCommandBuilder};
use anyhow::{Context, Error};
use beau_collector::BeauCollector;
use qapi::qmp::{QMP, Event};
use qapi::{Qmp, ExecuteError};
use qapi::{Qmp};
use std::{fmt, mem};
use std::fmt::{Debug, Formatter, Display};
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 {
pub fn new<P: AsRef<Path>>(
@ -111,6 +111,8 @@ impl VirtualMachine {
let mut results = vec![];
results.extend(self.prepare_disks());
results.extend(self.prepare_vfio(execute_fixes, force));
results.extend(self.prepare_shm());
results.extend(self.prepare_sockets());
results
.into_iter()
.bcollect::<()>()
@ -122,6 +124,52 @@ impl VirtualMachine {
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
///
@ -305,6 +353,14 @@ impl VirtualMachine {
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> {
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
@ -366,18 +422,6 @@ impl VirtualMachine {
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> {
self.wait(None, VirtualMachineState::Stopped)?;
Ok(())
@ -388,19 +432,12 @@ impl VirtualMachine {
return Ok(());
}
self.send_qmp_command(&qapi_qmp::quit {})
.map(|_| ())
.or_else(|x|
if let Some(ExecuteError::Io(err)) = x.downcast_ref::<ExecuteError>() {
if err.kind() == ErrorKind::UnexpectedEof {
Ok(())
} else {
Err(x)
}
} else {
Err(x)
})
.map_err(From::from)
match self.send_qmp_command(&qapi_qmp::quit {}) {
Err(err) if err.downcast_ref::<io::Error>().map_or(false, |x| x.kind() == io::ErrorKind::UnexpectedEof) => {}
err => { err?; }
}
Ok(())
}
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");
command.args(self.get_cmd_line()?);
command.args(self.get_cmd_line().context("Failed to generate qemu command line")?);
self.process = Some(command.spawn()?);
let mut res = || {
@ -477,7 +518,7 @@ impl VirtualMachine {
_info: handshake,
};
// self.pin_qemu_threads()?;
self.pin_qemu_threads()?;
control_socket
.qmp
@ -495,7 +536,7 @@ impl VirtualMachine {
let result_ = res();
if result_.is_err() {
if let Some(mut qemu) = self.process.take() {
qemu.kill()?;
let _ = qemu.kill();
qemu.wait()?;
}
}
@ -547,4 +588,4 @@ impl Write for CloneableUnixStream {
fn flush(&mut self) -> io::Result<()> {
self.lock()?.flush()
}
}
}

@ -54,4 +54,14 @@ impl Client {
self.send(PrepareRequest { name: vm, cdroms })?;
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;
use vore_core::{init_logging};
use vore_core::{init_logging, VirtualMachineInfo};
use crate::client::Client;
use clap::{App, ArgMatches};
use std::fs;
use std::{fs, mem};
use anyhow::Context;
use std::option::Option::Some;
use std::process::Command;
use std::os::unix::process::CommandExt;
fn main() {
init_logging();
@ -38,6 +40,18 @@ fn main_res() -> anyhow::Result<()> {
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)) => {
match args.subcommand() {
("version", _) => {
@ -60,7 +74,7 @@ fn main_res() -> anyhow::Result<()> {
struct LoadVMOptions {
config: String,
cdroms: Vec<String>,
cd_roms: Vec<String>,
save: bool,
}
@ -71,7 +85,7 @@ fn get_load_vm_options(args: &ArgMatches) -> anyhow::Result<LoadVMOptions> {
Ok(LoadVMOptions {
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"),
})
}
@ -82,12 +96,17 @@ struct VoreApp {
impl VoreApp {
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") {
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 {
let mut items = self.client.list_vms()?;
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"),
_ => anyhow::bail!("Multiple VM's are loaded, please specify one"),
}
@ -103,7 +122,7 @@ impl VoreApp {
fn load(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
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);
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<_>>()))?;
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)]
enum EventTarget {
RPCListener,
_Machine(String),
Machine(String),
RPCConnection(usize),
None,
}
@ -236,15 +236,43 @@ impl Daemon {
rpc::PrepareResponse {}.into_enum()
}
AllRequests::Start(_) => {
anyhow::bail!("Unimplemented");
AllRequests::Start(val) => {
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(_) => {
anyhow::bail!("Unimplemented");
AllRequests::Stop(val) => {
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(_) => {
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)
@ -273,8 +301,12 @@ impl Daemon {
self.poller.modify(&self.rpc_listener, Event::readable(event.key))?;
self.accept_rpc_connections()?;
}
EventTarget::_Machine(name) if self.machines.contains_key(&name) => {
if let Some(control_socket) = self.machines[&name].control_stream() {
EventTarget::Machine(name) if self.machines.contains_key(&name) => {
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))?;
}
}

Loading…
Cancel
Save