release when??
This commit is contained in:
parent
b5c4d38b77
commit
b538ff1657
18 changed files with 739 additions and 367 deletions
|
@ -5,7 +5,6 @@ features = [
|
||||||
"uefi",
|
"uefi",
|
||||||
"spice",
|
"spice",
|
||||||
"pulse",
|
"pulse",
|
||||||
# "scream",
|
|
||||||
"looking-glass"
|
"looking-glass"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
[vore]
|
||||||
|
group = "vore"
|
||||||
|
|
||||||
[qemu]
|
[qemu]
|
||||||
script = "qemu.lua"
|
script = "qemu.lua"
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ vore:set_build_command(function(instance, vm)
|
||||||
end
|
end
|
||||||
|
|
||||||
if instance.pulse.enabled then
|
if instance.pulse.enabled then
|
||||||
vm:arg("-device", "intel-hda", "-device", "hda-duplex")
|
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")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,10 @@ version = "0.1.0"
|
||||||
authors = ["eater <=@eater.me>"]
|
authors = ["eater <=@eater.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[features]
|
||||||
|
default = ["client"]
|
||||||
|
host = ["qapi", "qapi-qmp", "mlua"]
|
||||||
|
client = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
config = { version = "0.11.0", default-features = false, features = ["toml"] }
|
config = { version = "0.11.0", default-features = false, features = ["toml"] }
|
||||||
|
@ -13,10 +16,10 @@ serde_json = "1.0.64"
|
||||||
toml = "*"
|
toml = "*"
|
||||||
anyhow = "1.0.40"
|
anyhow = "1.0.40"
|
||||||
kiam = "0.1"
|
kiam = "0.1"
|
||||||
mlua = { version = "0.5.3", features = ["lua54", "serialize", "send"] }
|
mlua = { optional = true, version = "0.5.3", features = ["lua54", "serialize", "send"] }
|
||||||
beau_collector = "0.2.1"
|
beau_collector = "0.2.1"
|
||||||
qapi-qmp = "0.7.0"
|
qapi-qmp = { optional = true, version = "0.7.0" }
|
||||||
qapi = { version = "0.7.0", features = ["qapi-qmp"] }
|
qapi = { optional = true, version = "0.7.0", features = ["qapi-qmp"] }
|
||||||
libc = "0.2.94"
|
libc = "0.2.94"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
|
|
21
vore-core/src/consts.rs
Normal file
21
vore-core/src/consts.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// File with all static constants like e.g. paths
|
||||||
|
#![allow(clippy::manual_unwrap_or)]
|
||||||
|
|
||||||
|
macro_rules! default_env {
|
||||||
|
($val:expr, $def:expr) => {
|
||||||
|
match option_env!($val) {
|
||||||
|
None => $def,
|
||||||
|
Some(x) => x,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const VORE_DIRECTORY: &str = default_env!("VORE_DIRECTORY", "/var/lib/vore");
|
||||||
|
pub const VORE_SOCKET: &str = default_env!("VORE_SOCKET", "/run/vore.sock");
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub const VORE_CONFIG: &str = default_env!(
|
||||||
|
"VORE_CONFIG",
|
||||||
|
concat!(file!(), "/../../../../config/vored.toml")
|
||||||
|
);
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
pub const VORE_CONFIG: &str = default_env!("VORE_CONFIG", "/etc/vore/vored.toml");
|
|
@ -19,7 +19,7 @@ lazy_static! {
|
||||||
|
|
||||||
pub fn get_cpus() -> Vec<Cpu> {
|
pub fn get_cpus() -> Vec<Cpu> {
|
||||||
if cfg!(target_os = "linux") {
|
if cfg!(target_os = "linux") {
|
||||||
return crate::cpu_list::linux::get_cpus();
|
crate::cpu_list::linux::get_cpus()
|
||||||
} else {
|
} else {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ pub struct CpuList {
|
||||||
|
|
||||||
impl CpuList {
|
impl CpuList {
|
||||||
pub fn _get() -> CpuList {
|
pub fn _get() -> CpuList {
|
||||||
return *CPU_LIST;
|
*CPU_LIST
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _amount() -> usize {
|
pub fn _amount() -> usize {
|
||||||
|
|
|
@ -1,15 +1,69 @@
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::CString;
|
||||||
pub const GLOBAL_CONFIG_LOCATION: &str = "/home/eater/projects/vored/config/global.toml";
|
use std::fs;
|
||||||
|
use std::fs::Permissions;
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct GlobalConfig {
|
pub struct GlobalConfig {
|
||||||
|
pub vore: GlobalVoreConfig,
|
||||||
pub qemu: GlobalQemuConfig,
|
pub qemu: GlobalQemuConfig,
|
||||||
pub uefi: HashMap<String, GlobalUefiConfig>,
|
pub uefi: HashMap<String, GlobalUefiConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all(deserialize = "kebab-case"))]
|
||||||
|
pub struct GlobalVoreConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub group: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub unix_group_id: Option<libc::gid_t>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalVoreConfig {
|
||||||
|
pub fn get_gid(&mut self) -> Result<Option<u32>, anyhow::Error> {
|
||||||
|
if let Some(id) = self.unix_group_id {
|
||||||
|
return Ok(Some(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = self.group.as_ref().cloned();
|
||||||
|
|
||||||
|
name.map(|group_name| {
|
||||||
|
let group_name_c = CString::new(group_name.as_str())?;
|
||||||
|
Ok(unsafe {
|
||||||
|
let group = libc::getgrnam(group_name_c.as_ptr());
|
||||||
|
if group.is_null() {
|
||||||
|
anyhow::bail!("No group found with the name '{}'", group_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let gid = (*group).gr_gid;
|
||||||
|
|
||||||
|
self.unix_group_id = Some(gid);
|
||||||
|
|
||||||
|
gid
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chown(&mut self, path: &str) -> Result<(), anyhow::Error> {
|
||||||
|
if let Some(gid) = self.get_gid()? {
|
||||||
|
let meta = fs::metadata(path)?;
|
||||||
|
let path_c = CString::new(path)?;
|
||||||
|
unsafe {
|
||||||
|
libc::chown(path_c.as_ptr(), meta.uid(), gid);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::set_permissions(path, Permissions::from_mode(0o774))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct GlobalQemuConfig {
|
pub struct GlobalQemuConfig {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
|
|
|
@ -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};
|
||||||
|
@ -12,6 +12,7 @@ pub struct InstanceConfig {
|
||||||
pub arch: String,
|
pub arch: String,
|
||||||
pub chipset: String,
|
pub chipset: String,
|
||||||
pub kvm: bool,
|
pub kvm: bool,
|
||||||
|
pub auto_start: bool,
|
||||||
pub memory: u64,
|
pub memory: u64,
|
||||||
pub cpu: CpuConfig,
|
pub cpu: CpuConfig,
|
||||||
pub disks: Vec<DiskConfig>,
|
pub disks: Vec<DiskConfig>,
|
||||||
|
@ -46,6 +47,10 @@ impl InstanceConfig {
|
||||||
instance_config.memory = parse_size(&mem)?;
|
instance_config.memory = parse_size(&mem)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(auto_start) = config.get_bool("machine.auto-start") {
|
||||||
|
instance_config.auto_start = auto_start;
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(cpu) = config.get_table("cpu") {
|
if let Ok(cpu) = config.get_table("cpu") {
|
||||||
instance_config.cpu.apply_table(cpu)?
|
instance_config.cpu.apply_table(cpu)?
|
||||||
}
|
}
|
||||||
|
@ -74,16 +79,15 @@ impl InstanceConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
instance_config.looking_glass = LookingGlassConfig::from_table(
|
instance_config.looking_glass =
|
||||||
config.get_table("looking-glass").unwrap_or_default(),
|
LookingGlassConfig::from_table(config.get_table("looking-glass").unwrap_or_default())?;
|
||||||
)?;
|
instance_config.scream =
|
||||||
instance_config.scream = ScreamConfig::from_table(
|
ScreamConfig::from_table(config.get_table("scream").unwrap_or_default())?;
|
||||||
config.get_table("scream").unwrap_or_default(),
|
|
||||||
)?;
|
|
||||||
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())?;
|
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 {
|
||||||
|
@ -109,6 +113,7 @@ impl Default for InstanceConfig {
|
||||||
arch: std::env::consts::ARCH.to_string(),
|
arch: std::env::consts::ARCH.to_string(),
|
||||||
chipset: "q35".to_string(),
|
chipset: "q35".to_string(),
|
||||||
kvm: true,
|
kvm: true,
|
||||||
|
auto_start: false,
|
||||||
// 2 GB
|
// 2 GB
|
||||||
memory: 2 * 1024 * 1024 * 1024,
|
memory: 2 * 1024 * 1024 * 1024,
|
||||||
cpu: Default::default(),
|
cpu: Default::default(),
|
||||||
|
@ -191,24 +196,20 @@ impl CpuConfig {
|
||||||
|
|
||||||
if !table.contains_key("amount") {
|
if !table.contains_key("amount") {
|
||||||
self.amount = self.sockets * self.dies * self.cores * self.threads;
|
self.amount = self.sockets * self.dies * self.cores * self.threads;
|
||||||
} else {
|
} else if table
|
||||||
if table
|
|
||||||
.keys()
|
.keys()
|
||||||
.any(|x| ["cores", "sockets", "dies", "threads"].contains(&x.as_str()))
|
.any(|x| ["cores", "sockets", "dies", "threads"].contains(&x.as_str()))
|
||||||
{
|
{
|
||||||
let calc_amount = self.sockets * self.dies * self.cores * self.threads;
|
let calc_amount = self.sockets * self.dies * self.cores * self.threads;
|
||||||
if self.amount != calc_amount {
|
if self.amount != calc_amount {
|
||||||
Err(anyhow::Error::msg(format!("Amount of cpu's ({}) from sockets ({}), dies ({}), cores ({}) and threads ({}) differs from specified ({}) cpu's", calc_amount, self.sockets, self.dies, self.cores, self.threads, self.amount)))?;
|
return Err(anyhow::Error::msg(format!("Amount of cpu's ({}) from sockets ({}), dies ({}), cores ({}) and threads ({}) differs from specified ({}) cpu's", calc_amount, self.sockets, self.dies, self.cores, self.threads, self.amount)));
|
||||||
}
|
}
|
||||||
} else {
|
} else if (self.amount % 2) == 0 {
|
||||||
if (self.amount % 2) == 0 {
|
|
||||||
self.cores = self.amount / 2;
|
self.cores = self.amount / 2;
|
||||||
} else {
|
} else {
|
||||||
self.threads = 1;
|
self.threads = 1;
|
||||||
self.cores = self.amount;
|
self.cores = self.amount;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -240,7 +241,7 @@ fn parse_size(orig_input: &str) -> Result<u64, anyhow::Error> {
|
||||||
input = &input[..input.len() - 1];
|
input = &input[..input.len() - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.len() == 0 {
|
if input.is_empty() {
|
||||||
return Err(anyhow::Error::msg(format!(
|
return Err(anyhow::Error::msg(format!(
|
||||||
"'{}' is not a valid size",
|
"'{}' is not a valid size",
|
||||||
orig_input
|
orig_input
|
||||||
|
@ -286,9 +287,7 @@ pub struct ScreamConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScreamConfig {
|
impl ScreamConfig {
|
||||||
pub fn from_table(
|
pub fn from_table(table: HashMap<String, Value>) -> Result<ScreamConfig, anyhow::Error> {
|
||||||
table: HashMap<String, Value>,
|
|
||||||
) -> 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() {
|
||||||
cfg.enabled = enabled.into_bool()?;
|
cfg.enabled = enabled.into_bool()?;
|
||||||
|
@ -368,9 +367,7 @@ impl LookingGlassConfig {
|
||||||
self.buffer_size = buffer_size;
|
self.buffer_size = buffer_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_table(
|
pub fn from_table(table: HashMap<String, Value>) -> Result<LookingGlassConfig, anyhow::Error> {
|
||||||
table: HashMap<String, Value>,
|
|
||||||
) -> Result<LookingGlassConfig, anyhow::Error> {
|
|
||||||
let mut cfg = LookingGlassConfig::default();
|
let mut cfg = LookingGlassConfig::default();
|
||||||
|
|
||||||
if let Some(enabled) = table.get("enabled").cloned() {
|
if let Some(enabled) = table.get("enabled").cloned() {
|
||||||
|
@ -434,12 +431,14 @@ impl DiskConfig {
|
||||||
}).to_string()
|
}).to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let preset = table.get("preset")
|
let preset = table
|
||||||
|
.get("preset")
|
||||||
.cloned()
|
.cloned()
|
||||||
.context("Every disk should have a preset set")?
|
.context("Every disk should have a preset set")?
|
||||||
.into_str()?;
|
.into_str()?;
|
||||||
|
|
||||||
let read_only = table.get("read-only")
|
let read_only = table
|
||||||
|
.get("read-only")
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|x| x.into_bool())
|
.map(|x| x.into_bool())
|
||||||
.transpose()
|
.transpose()
|
||||||
|
@ -459,15 +458,16 @@ impl DiskConfig {
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct VfioConfig {
|
pub struct VfioConfig {
|
||||||
pub address: PCIAddress,
|
pub address: PciAddress,
|
||||||
pub vendor: Option<u32>,
|
pub vendor: Option<u32>,
|
||||||
pub device: Option<u32>,
|
pub device: Option<u32>,
|
||||||
pub index: u32,
|
pub index: u32,
|
||||||
pub graphics: bool,
|
pub graphics: bool,
|
||||||
pub multifunction: bool,
|
pub multifunction: bool,
|
||||||
|
pub reserve: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_pci_ids(addr: &PCIAddress) -> Result<(u32, u32), anyhow::Error> {
|
pub fn read_pci_ids(addr: &PciAddress) -> Result<(u32, u32), anyhow::Error> {
|
||||||
let device = std::fs::read_to_string(format!("/sys/bus/pci/devices/{:#}/device", addr))
|
let device = std::fs::read_to_string(format!("/sys/bus/pci/devices/{:#}/device", addr))
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
|
@ -497,7 +497,7 @@ impl VfioConfig {
|
||||||
.get("addr")
|
.get("addr")
|
||||||
.or_else(|| table.get("address"))
|
.or_else(|| table.get("address"))
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|x| PCIAddress::from_str(&x.into_str()?))
|
.map(|x| PciAddress::from_str(&x.into_str()?))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let vendor = table
|
let vendor = table
|
||||||
|
@ -548,7 +548,7 @@ impl VfioConfig {
|
||||||
|
|
||||||
(None, Some(vendor), Some(device)) => {
|
(None, Some(vendor), Some(device)) => {
|
||||||
let mut counter = index;
|
let mut counter = index;
|
||||||
let mut items: Vec<(PCIAddress, u32, u32)> = vec![];
|
let mut items: Vec<(PciAddress, u32, u32)> = vec![];
|
||||||
|
|
||||||
for entry in std::fs::read_dir("/sys/bus/pci/devices")? {
|
for entry in std::fs::read_dir("/sys/bus/pci/devices")? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
|
@ -556,7 +556,7 @@ impl VfioConfig {
|
||||||
let addr_name = file_name
|
let addr_name = file_name
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or_else(|| anyhow::anyhow!("Failed to parse PCI device name"))?;
|
.ok_or_else(|| anyhow::anyhow!("Failed to parse PCI device name"))?;
|
||||||
let addr = PCIAddress::from_str(addr_name)?;
|
let addr = PciAddress::from_str(addr_name)?;
|
||||||
let (found_vendor, found_device) = read_pci_ids(&addr)?;
|
let (found_vendor, found_device) = read_pci_ids(&addr)?;
|
||||||
items.push((addr, found_vendor, found_device));
|
items.push((addr, found_vendor, found_device));
|
||||||
}
|
}
|
||||||
|
@ -596,6 +596,7 @@ impl VfioConfig {
|
||||||
index: 0,
|
index: 0,
|
||||||
graphics: false,
|
graphics: false,
|
||||||
multifunction: false,
|
multifunction: false,
|
||||||
|
reserve: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(graphics) = table.get("graphics").cloned() {
|
if let Some(graphics) = table.get("graphics").cloned() {
|
||||||
|
@ -606,6 +607,10 @@ impl VfioConfig {
|
||||||
cfg.multifunction = multifunction.into_bool()?;
|
cfg.multifunction = multifunction.into_bool()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(reserve) = table.get("reserve").cloned() {
|
||||||
|
cfg.reserve = reserve.into_bool()?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -617,9 +622,7 @@ pub struct PulseConfig {
|
||||||
|
|
||||||
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 {
|
let mut cfg = PulseConfig { enabled: false };
|
||||||
enabled: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
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()?;
|
||||||
|
@ -629,7 +632,6 @@ impl PulseConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
pub struct SpiceConfig {
|
pub struct SpiceConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
@ -656,14 +658,14 @@ impl SpiceConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
#[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
pub struct PCIAddress {
|
pub struct PciAddress {
|
||||||
domain: u32,
|
domain: u32,
|
||||||
bus: u8,
|
bus: u8,
|
||||||
slot: u8,
|
slot: u8,
|
||||||
func: u8,
|
func: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for PCIAddress {
|
impl<'de> Deserialize<'de> for PciAddress {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
|
@ -676,8 +678,10 @@ 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
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
E: de::Error, {
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
Ok(v.to_string())
|
Ok(v.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,21 +691,22 @@ impl<'de> Deserialize<'de> for PCIAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = deserializer.deserialize_string(X)?;
|
let x = deserializer.deserialize_string(X)?;
|
||||||
Ok(PCIAddress::from_str(&x).map_err(|x| de::Error::custom(x))?)
|
|
||||||
|
PciAddress::from_str(&x).map_err(de::Error::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for PCIAddress {
|
impl Serialize for PciAddress {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
serializer.serialize_str(&self.to_string())
|
serializer.serialize_str(&self.to_pci_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PCIAddress {
|
impl PciAddress {
|
||||||
fn to_string(&self) -> String {
|
fn to_pci_string(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{:04x}:{:02x}:{:02x}.{:x}",
|
"{:04x}:{:02x}:{:02x}.{:x}",
|
||||||
self.domain, self.bus, self.slot, self.func
|
self.domain, self.bus, self.slot, self.func
|
||||||
|
@ -709,7 +714,7 @@ impl PCIAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for PCIAddress {
|
impl Debug for PciAddress {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str("PCIAddress(")?;
|
f.write_str("PCIAddress(")?;
|
||||||
if f.alternate() && self.domain == 0 {
|
if f.alternate() && self.domain == 0 {
|
||||||
|
@ -724,7 +729,7 @@ impl Debug for PCIAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for PCIAddress {
|
impl Display for PciAddress {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
if f.alternate() && self.domain == 0 {
|
if f.alternate() && self.domain == 0 {
|
||||||
f.write_str(&format!("{:04x}:", self.domain))?;
|
f.write_str(&format!("{:04x}:", self.domain))?;
|
||||||
|
@ -737,15 +742,15 @@ impl Display for PCIAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for PCIAddress {
|
impl FromStr for PciAddress {
|
||||||
type Err = anyhow::Error;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut rev = s.rsplit(":");
|
let mut rev = s.rsplit(':');
|
||||||
let mut addr = PCIAddress::default();
|
let mut addr = PciAddress::default();
|
||||||
|
|
||||||
if let Some(slot_and_func) = rev.next() {
|
if let Some(slot_and_func) = rev.next() {
|
||||||
let mut splitter = slot_and_func.split(".");
|
let mut splitter = slot_and_func.split('.');
|
||||||
|
|
||||||
if let Some(slot) = splitter.next() {
|
if let Some(slot) = splitter.next() {
|
||||||
addr.slot = u8::from_str_radix(slot, 16)?;
|
addr.slot = u8::from_str_radix(slot, 16)?;
|
||||||
|
@ -770,20 +775,20 @@ impl FromStr for PCIAddress {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::PCIAddress;
|
use crate::PciAddress;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_input_and_output_are_same() {
|
fn test_input_and_output_are_same() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PCIAddress::from_str("0000:00:00.1")
|
PciAddress::from_str("0000:00:00.1")
|
||||||
.expect("Failed to parse correct string")
|
.expect("Failed to parse correct string")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
"0000:00:00.1"
|
"0000:00:00.1"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PCIAddress::from_str("0000:00:01.0")
|
PciAddress::from_str("0000:00:01.0")
|
||||||
.expect("Failed to parse correct string")
|
.expect("Failed to parse correct string")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
"0000:00:01.0"
|
"0000:00:01.0"
|
||||||
|
|
|
@ -4,18 +4,22 @@ mod qemu;
|
||||||
mod virtual_machine;
|
mod virtual_machine;
|
||||||
mod cpu_list;
|
mod cpu_list;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
|
pub mod consts;
|
||||||
|
mod virtual_machine_info;
|
||||||
|
|
||||||
pub use global_config::*;
|
pub use global_config::*;
|
||||||
pub use instance_config::*;
|
pub use instance_config::*;
|
||||||
pub use qemu::QemuCommandBuilder;
|
pub use qemu::QemuCommandBuilder;
|
||||||
|
#[cfg(feature = "host")]
|
||||||
pub use virtual_machine::*;
|
pub use virtual_machine::*;
|
||||||
use log::LevelFilter;
|
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;
|
||||||
builder.filter_level(LevelFilter::Debug);
|
builder.filter_level(LevelFilter::Debug);
|
||||||
}
|
}
|
||||||
builder.parse_filters(&std::env::var("RUST_LOG").unwrap_or("".to_string()));
|
builder.parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| "".to_string()));
|
||||||
builder.init();
|
builder.init();
|
||||||
}
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::{GlobalConfig, InstanceConfig, GLOBAL_CONFIG_LOCATION};
|
#![cfg(feature = "host")]
|
||||||
|
|
||||||
|
use crate::consts::VORE_CONFIG;
|
||||||
|
use crate::{GlobalConfig, InstanceConfig};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use mlua::prelude::LuaError;
|
use mlua::prelude::LuaError;
|
||||||
use mlua::{
|
use mlua::{
|
||||||
|
@ -8,31 +11,30 @@ 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::path::{PathBuf, Path};
|
|
||||||
use std::sync::{Arc, Mutex, Weak};
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Clone)]
|
#[derive(Debug, Default, Deserialize, Clone)]
|
||||||
struct VM {
|
struct VirtualMachine {
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
bus_ids: HashMap<String, usize>,
|
bus_ids: HashMap<String, usize>,
|
||||||
devices: HashMap<String, String>,
|
devices: HashMap<String, String>,
|
||||||
device: bool,
|
device: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserData for VM {
|
impl UserData for VirtualMachine {
|
||||||
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_mut("arg", |_, this, args: MultiValue| {
|
methods.add_method_mut("arg", |_, this, args: MultiValue| {
|
||||||
for item in args.iter() {
|
for item in args.iter() {
|
||||||
if let Value::String(item) = item {
|
if let Value::String(item) = item {
|
||||||
let item = item.to_str()?.to_string();
|
let item = item.to_str()?.to_string();
|
||||||
if this.device {
|
if this.device {
|
||||||
let mut items = item.split(",");
|
let mut items = item.split(',');
|
||||||
if let Some(_type) = items.next() {
|
if let Some(_type) = items.next() {
|
||||||
for item in items {
|
for item in items {
|
||||||
if item.starts_with("id=") {
|
if let Some(id) = item.strip_prefix("id=") {
|
||||||
this.devices
|
this.devices.insert(_type.to_string(), id.to_string());
|
||||||
.insert(_type.to_string(), item[3..].to_string());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,15 +61,13 @@ impl UserData for VM {
|
||||||
});
|
});
|
||||||
|
|
||||||
methods.add_method_mut("get_next_bus", |lua, this, name: String| {
|
methods.add_method_mut("get_next_bus", |lua, this, name: String| {
|
||||||
format!(
|
let id = this
|
||||||
"{}.{}",
|
.bus_ids
|
||||||
name.clone(),
|
.entry(name.clone())
|
||||||
this.bus_ids
|
|
||||||
.entry(name)
|
|
||||||
.and_modify(|x| *x += 1)
|
.and_modify(|x| *x += 1)
|
||||||
.or_insert(0)
|
.or_insert(0);
|
||||||
)
|
|
||||||
.to_lua(lua)
|
format!("{}.{}", name, id).to_lua(lua)
|
||||||
});
|
});
|
||||||
|
|
||||||
methods.add_method_mut("get_counter", |lua, this, args: (String, usize)| {
|
methods.add_method_mut("get_counter", |lua, this, args: (String, usize)| {
|
||||||
|
@ -107,7 +107,7 @@ impl UserData for VoreLuaWeakStorage {
|
||||||
let strong = weak
|
let strong = weak
|
||||||
.0
|
.0
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(LuaError::custom("vore storage has expired"))?;
|
.ok_or_else(|| LuaError::custom("vore storage has expired"))?;
|
||||||
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"))?;
|
||||||
|
@ -126,7 +126,7 @@ impl UserData for VoreLuaWeakStorage {
|
||||||
let strong = weak
|
let strong = weak
|
||||||
.0
|
.0
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(LuaError::custom("vore storage has expired"))?;
|
.ok_or_else(|| LuaError::custom("vore storage has expired"))?;
|
||||||
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"))?;
|
||||||
|
@ -145,7 +145,7 @@ impl UserData for VoreLuaWeakStorage {
|
||||||
let strong = weak
|
let strong = weak
|
||||||
.0
|
.0
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(LuaError::custom("vore storage has expired"))?;
|
.ok_or_else(|| LuaError::custom("vore storage has expired"))?;
|
||||||
let this = strong
|
let this = strong
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
||||||
|
@ -168,13 +168,16 @@ impl UserData for VoreLuaWeakStorage {
|
||||||
|
|
||||||
methods.add_method(
|
methods.add_method(
|
||||||
"add_disk",
|
"add_disk",
|
||||||
|lua, weak, args: (VM, mlua::Table, u64, mlua::Table)| -> Result<Value, mlua::Error> {
|
|lua,
|
||||||
let (vm, instance, index, disk): (VM, mlua::Table, u64, Table) = args;
|
weak,
|
||||||
|
args: (VirtualMachine, mlua::Table, u64, mlua::Table)|
|
||||||
|
-> Result<Value, mlua::Error> {
|
||||||
|
let (vm, instance, index, disk): (VirtualMachine, mlua::Table, u64, Table) = args;
|
||||||
let function = {
|
let function = {
|
||||||
let strong = weak
|
let strong = weak
|
||||||
.0
|
.0
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(LuaError::custom("vore storage has expired"))?;
|
.ok_or_else(|| LuaError::custom("vore storage has expired"))?;
|
||||||
let this = strong
|
let this = strong
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
||||||
|
@ -223,11 +226,16 @@ 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).parent().unwrap().join(&global.qemu.script);
|
let lua = Path::new(VORE_CONFIG)
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join(&global.qemu.script);
|
||||||
|
|
||||||
let builder = QemuCommandBuilder {
|
let builder = QemuCommandBuilder {
|
||||||
lua: Lua::new(),
|
lua: Lua::new(),
|
||||||
script: fs::read_to_string(&lua).with_context(|| format!("Failed to load lua qemu command build script ({:?})", 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),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -260,7 +268,7 @@ impl QemuCommandBuilder {
|
||||||
.eval::<()>()
|
.eval::<()>()
|
||||||
.context("Failed to run the configured qemu lua script")?;
|
.context("Failed to run the configured qemu lua script")?;
|
||||||
|
|
||||||
let item = VM::default();
|
let item = VirtualMachine::default();
|
||||||
let multi = MultiValue::from_vec(vec![self.lua.to_value(config)?, item.to_lua(&self.lua)?]);
|
let multi = MultiValue::from_vec(vec![self.lua.to_value(config)?, item.to_lua(&self.lua)?]);
|
||||||
|
|
||||||
let working_dir = { self.storage.0.lock().unwrap().working_dir.clone() };
|
let working_dir = { self.storage.0.lock().unwrap().working_dir.clone() };
|
||||||
|
@ -277,22 +285,21 @@ impl QemuCommandBuilder {
|
||||||
anyhow::bail!("No qemu build command registered in lua script");
|
anyhow::bail!("No qemu build command registered in lua script");
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut vm_instance = build_command.call::<MultiValue, VM>(multi)?;
|
let mut vm_instance = build_command.call::<MultiValue, VirtualMachine>(multi)?;
|
||||||
|
|
||||||
let mut cmd: Vec<String> = vec![];
|
|
||||||
cmd.push("-name".to_string());
|
|
||||||
cmd.push(format!("guest={},debug-threads=on", config.name));
|
|
||||||
|
|
||||||
|
// Weird building way is for clarity sake
|
||||||
|
let mut cmd: Vec<String> = vec![
|
||||||
|
"-name".into(),
|
||||||
|
format!("guest={},debug-threads=on", config.name),
|
||||||
// Don't start the machine
|
// Don't start the machine
|
||||||
cmd.push("-S".to_string());
|
"-S".into(),
|
||||||
|
|
||||||
// Set timestamps on log
|
// Set timestamps on log
|
||||||
cmd.push("-msg".to_string());
|
"-msg".into(),
|
||||||
cmd.push("timestamp=on".to_string());
|
"timestamp=on".into(),
|
||||||
|
|
||||||
// Drop privileges as soon as possible
|
// Drop privileges as soon as possible
|
||||||
cmd.push("-runas".to_string());
|
"-runas".into(),
|
||||||
cmd.push("nobody".to_string());
|
"nobody".into(),
|
||||||
|
];
|
||||||
|
|
||||||
let working_dir = working_dir
|
let working_dir = working_dir
|
||||||
.to_str()
|
.to_str()
|
||||||
|
|
|
@ -9,13 +9,13 @@ macro_rules! define_requests {
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "query", rename_all = "snake_case")]
|
#[serde(tag = "query", rename_all = "snake_case")]
|
||||||
pub enum AllRequests {
|
pub enum AllRequests {
|
||||||
$($name(paste! { [<$name Request >] })),+
|
$($name(Box<paste! { [<$name Request >] }>)),+
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "answer", rename_all = "snake_case")]
|
#[serde(tag = "answer", rename_all = "snake_case")]
|
||||||
pub enum AllResponses {
|
pub enum AllResponses {
|
||||||
$($name(paste! { [<$name Response >] })),+
|
$($name(Box<paste! { [<$name Response >] }>)),+
|
||||||
}
|
}
|
||||||
|
|
||||||
$(
|
$(
|
||||||
|
@ -29,13 +29,13 @@ macro_rules! define_requests {
|
||||||
type Response = [<$name Response>];
|
type Response = [<$name Response>];
|
||||||
|
|
||||||
fn into_enum(self) -> AllRequests {
|
fn into_enum(self) -> AllRequests {
|
||||||
AllRequests::$name(self)
|
AllRequests::$name(Box::new(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response for [<$name Response>] {
|
impl Response for [<$name Response>] {
|
||||||
fn into_enum(self) -> AllResponses {
|
fn into_enum(self) -> AllResponses {
|
||||||
AllResponses::$name(self)
|
AllResponses::$name(Box::new(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ impl CommandCenter {
|
||||||
|
|
||||||
let mut str = serde_json::to_string(&answer)?;
|
let mut str = serde_json::to_string(&answer)?;
|
||||||
str.push('\n');
|
str.push('\n');
|
||||||
return Ok(str);
|
Ok(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_command(request: &str) -> Result<Command, anyhow::Error> {
|
pub fn read_command(request: &str) -> Result<Command, anyhow::Error> {
|
||||||
|
|
|
@ -1,56 +1,31 @@
|
||||||
use crate::{GlobalConfig, InstanceConfig, QemuCommandBuilder};
|
#![cfg(feature = "host")]
|
||||||
|
|
||||||
|
use crate::cpu_list::CpuList;
|
||||||
|
use crate::{
|
||||||
|
GlobalConfig, InstanceConfig, QemuCommandBuilder, VfioConfig, VirtualMachineInfo,
|
||||||
|
VirtualMachineState,
|
||||||
|
};
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
use beau_collector::BeauCollector;
|
use beau_collector::BeauCollector;
|
||||||
use qapi::qmp::{QMP, Event};
|
use libc::{cpu_set_t, sched_setaffinity, CPU_SET};
|
||||||
use qapi::{Qmp};
|
use qapi::qmp::{Event, QMP};
|
||||||
use std::{fmt, mem};
|
use qapi::Qmp;
|
||||||
use std::fmt::{Debug, Formatter, Display};
|
use qapi_qmp::QmpCommand;
|
||||||
use std::fs::{read_link, OpenOptions, read_dir};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::fs::{read_dir, read_link, OpenOptions};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{BufReader, ErrorKind, Read, Write};
|
use std::io::{BufReader, ErrorKind, Read, Write};
|
||||||
use std::option::Option::Some;
|
use std::option::Option::Some;
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::path::{PathBuf, Path};
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Child, Command};
|
use std::process::{Child, Command};
|
||||||
use std::result::Result::Ok;
|
use std::result::Result::Ok;
|
||||||
|
use std::slice::Iter;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use qapi_qmp::QmpCommand;
|
use std::{fmt, mem};
|
||||||
use std::str::FromStr;
|
|
||||||
use libc::{cpu_set_t, CPU_SET, sched_setaffinity};
|
|
||||||
use crate::cpu_list::CpuList;
|
|
||||||
use std::os::unix::prelude::AsRawFd;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum VirtualMachineState {
|
|
||||||
Loaded,
|
|
||||||
Prepared,
|
|
||||||
Stopped,
|
|
||||||
Paused,
|
|
||||||
Running,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for VirtualMachineState {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
VirtualMachineState::Loaded => write!(f, "loaded"),
|
|
||||||
VirtualMachineState::Prepared => write!(f, "prepared"),
|
|
||||||
VirtualMachineState::Stopped => write!(f, "stopped"),
|
|
||||||
VirtualMachineState::Paused => write!(f, "paused"),
|
|
||||||
VirtualMachineState::Running => write!(f, "running")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
pub struct VirtualMachineInfo {
|
|
||||||
pub name: String,
|
|
||||||
pub working_dir: PathBuf,
|
|
||||||
pub config: InstanceConfig,
|
|
||||||
pub state: VirtualMachineState,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VirtualMachine {
|
pub struct VirtualMachine {
|
||||||
|
@ -60,6 +35,7 @@ pub struct VirtualMachine {
|
||||||
global_config: GlobalConfig,
|
global_config: GlobalConfig,
|
||||||
process: Option<Child>,
|
process: Option<Child>,
|
||||||
control_socket: Option<ControlSocket>,
|
control_socket: Option<ControlSocket>,
|
||||||
|
quit_after_shutdown: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ControlSocket {
|
struct ControlSocket {
|
||||||
|
@ -91,9 +67,14 @@ impl VirtualMachine {
|
||||||
global_config: global_config.clone(),
|
global_config: global_config.clone(),
|
||||||
process: None,
|
process: None,
|
||||||
control_socket: None,
|
control_socket: None,
|
||||||
|
quit_after_shutdown: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn vfio_devices(&self) -> Iter<'_, VfioConfig> {
|
||||||
|
self.config.vfio.iter()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
&self.config.name
|
&self.config.name
|
||||||
}
|
}
|
||||||
|
@ -104,6 +85,7 @@ impl VirtualMachine {
|
||||||
working_dir: self.working_dir.clone(),
|
working_dir: self.working_dir.clone(),
|
||||||
config: self.config.clone(),
|
config: self.config.clone(),
|
||||||
state: self.state,
|
state: self.state,
|
||||||
|
quit_after_shutdown: self.quit_after_shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +110,8 @@ impl VirtualMachine {
|
||||||
let mut shm = vec![];
|
let mut shm = vec![];
|
||||||
if self.config.looking_glass.enabled {
|
if self.config.looking_glass.enabled {
|
||||||
if self.config.looking_glass.mem_path.is_empty() {
|
if self.config.looking_glass.mem_path.is_empty() {
|
||||||
self.config.looking_glass.mem_path = format!("/dev/shm/vore/{}/looking-glass", self.config.name);
|
self.config.looking_glass.mem_path =
|
||||||
|
format!("/dev/shm/vore/{}/looking-glass", self.config.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
shm.push(&self.config.looking_glass.mem_path);
|
shm.push(&self.config.looking_glass.mem_path);
|
||||||
|
@ -142,12 +125,15 @@ impl VirtualMachine {
|
||||||
shm.push(&self.config.scream.mem_path);
|
shm.push(&self.config.scream.mem_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
shm
|
shm.into_iter()
|
||||||
.into_iter()
|
|
||||||
.map(|x| Path::new(x))
|
.map(|x| Path::new(x))
|
||||||
.filter_map(|x| x.parent())
|
.filter_map(|x| x.parent())
|
||||||
.filter(|x| !x.is_dir())
|
.filter(|x| !x.is_dir())
|
||||||
.map(|x| std::fs::create_dir_all(&x).with_context(|| format!("Failed creating directories for shared memory ({:?})", x)))
|
.map(|x| {
|
||||||
|
std::fs::create_dir_all(&x).with_context(|| {
|
||||||
|
format!("Failed creating directories for shared memory ({:?})", x)
|
||||||
|
})
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +141,12 @@ impl VirtualMachine {
|
||||||
let mut sockets = vec![];
|
let mut sockets = vec![];
|
||||||
if self.config.spice.enabled {
|
if self.config.spice.enabled {
|
||||||
if self.config.spice.socket_path.is_empty() {
|
if self.config.spice.socket_path.is_empty() {
|
||||||
self.config.spice.socket_path = self.working_dir.join("spice.sock").to_str().unwrap().to_string();
|
self.config.spice.socket_path = self
|
||||||
|
.working_dir
|
||||||
|
.join("spice.sock")
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
sockets.push(&self.config.spice.socket_path);
|
sockets.push(&self.config.spice.socket_path);
|
||||||
|
@ -166,7 +157,11 @@ impl VirtualMachine {
|
||||||
.map(|x| Path::new(x))
|
.map(|x| Path::new(x))
|
||||||
.filter_map(|x| x.parent())
|
.filter_map(|x| x.parent())
|
||||||
.filter(|x| !x.is_dir())
|
.filter(|x| !x.is_dir())
|
||||||
.map(|x| std::fs::create_dir_all(&x).with_context(|| format!("Failed creating directories for shared memory ({:?})", x)))
|
.map(|x| {
|
||||||
|
std::fs::create_dir_all(&x).with_context(|| {
|
||||||
|
format!("Failed creating directories for shared memory ({:?})", x)
|
||||||
|
})
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +209,22 @@ impl VirtualMachine {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.config.vfio.iter().map(|vfio| {
|
self.config
|
||||||
|
.vfio
|
||||||
|
.iter()
|
||||||
|
.map(|vfio| VirtualMachine::prepare_vfio_device(execute_fixes, force, vfio))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_auto_start(&self) -> bool {
|
||||||
|
self.config.auto_start
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_vfio_device(
|
||||||
|
execute_fixes: bool,
|
||||||
|
force: bool,
|
||||||
|
vfio: &VfioConfig,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let pci_driver_path = format!("/sys/bus/pci/devices/{:#}/driver", vfio.address);
|
let pci_driver_path = format!("/sys/bus/pci/devices/{:#}/driver", vfio.address);
|
||||||
|
|
||||||
let driver = match read_link(&pci_driver_path) {
|
let driver = match read_link(&pci_driver_path) {
|
||||||
|
@ -225,7 +235,7 @@ impl VirtualMachine {
|
||||||
vfio.address
|
vfio.address
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let driver = driver_path.split("/").last().ok_or_else(|| {
|
let driver = driver_path.split('/').last().ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"Path to device driver for PCI device at {} doesn't have a path to a driver",
|
"Path to device driver for PCI device at {} doesn't have a path to a driver",
|
||||||
vfio.address
|
vfio.address
|
||||||
|
@ -290,8 +300,6 @@ impl VirtualMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cmd_line(&self) -> Result<Vec<String>, anyhow::Error> {
|
pub fn get_cmd_line(&self) -> Result<Vec<String>, anyhow::Error> {
|
||||||
|
@ -321,7 +329,11 @@ impl VirtualMachine {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = entry.file_name().to_str().ok_or_else(|| anyhow::anyhow!("")).and_then(|x| usize::from_str(x).map_err(From::from));
|
let res = entry
|
||||||
|
.file_name()
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!(""))
|
||||||
|
.and_then(|x| usize::from_str(x).map_err(From::from));
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -330,7 +342,11 @@ impl VirtualMachine {
|
||||||
let name = entry.path().join("comm");
|
let name = entry.path().join("comm");
|
||||||
let comm = std::fs::read_to_string(name)?;
|
let comm = std::fs::read_to_string(name)?;
|
||||||
if comm.starts_with("CPU ") {
|
if comm.starts_with("CPU ") {
|
||||||
let nr = comm.chars().skip(4).take_while(|x| x.is_ascii_digit()).collect::<String>();
|
let nr = comm
|
||||||
|
.chars()
|
||||||
|
.skip(4)
|
||||||
|
.take_while(|x| x.is_ascii_digit())
|
||||||
|
.collect::<String>();
|
||||||
let cpu_id = usize::from_str(&nr).unwrap();
|
let cpu_id = usize::from_str(&nr).unwrap();
|
||||||
kvm_threads.push((tid, cpu_id));
|
kvm_threads.push((tid, cpu_id));
|
||||||
}
|
}
|
||||||
|
@ -358,10 +374,12 @@ impl VirtualMachine {
|
||||||
qmp.qmp.nop()?;
|
qmp.qmp.nop()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.process_qmp_events()
|
self.process_qmp_events()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_qmp_events(&mut self) -> Result<(), anyhow::Error> {
|
fn process_qmp_events(&mut self) -> anyhow::Result<()> {
|
||||||
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
|
||||||
qmp.qmp.events().collect::<Vec<_>>()
|
qmp.qmp.events().collect::<Vec<_>>()
|
||||||
|
@ -370,11 +388,11 @@ impl VirtualMachine {
|
||||||
};
|
};
|
||||||
|
|
||||||
for event in events {
|
for event in events {
|
||||||
println!("Event: {:?}", event);
|
log::info!("vm {} got event: {:?}", self.name(), event);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::STOP { .. } => {
|
Event::STOP { .. } => {
|
||||||
if self.state != VirtualMachineState::Stopped {
|
if self.state == VirtualMachineState::Running {
|
||||||
self.state = VirtualMachineState::Paused;
|
self.state = VirtualMachineState::Paused;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,6 +401,10 @@ impl VirtualMachine {
|
||||||
}
|
}
|
||||||
Event::SHUTDOWN { .. } => {
|
Event::SHUTDOWN { .. } => {
|
||||||
self.state = VirtualMachineState::Stopped;
|
self.state = VirtualMachineState::Stopped;
|
||||||
|
|
||||||
|
if self.quit_after_shutdown {
|
||||||
|
self.quit()?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -414,7 +436,10 @@ impl VirtualMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self) -> Result<(), anyhow::Error> {
|
pub fn stop(&mut self) -> Result<(), anyhow::Error> {
|
||||||
if self.process.is_none() || self.control_socket.is_none() || self.state == VirtualMachineState::Stopped {
|
if self.process.is_none()
|
||||||
|
|| self.control_socket.is_none()
|
||||||
|
|| self.state == VirtualMachineState::Stopped
|
||||||
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,17 +458,32 @@ impl VirtualMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.send_qmp_command(&qapi_qmp::quit {}) {
|
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)
|
||||||
err => { err?; }
|
if err.downcast_ref::<io::Error>().map_or(false, |x| {
|
||||||
|
x.kind() == io::ErrorKind::UnexpectedEof
|
||||||
|
|| x.kind() == io::ErrorKind::ConnectionReset
|
||||||
|
}) => {}
|
||||||
|
err => {
|
||||||
|
err?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
self.control_socket = None;
|
||||||
|
|
||||||
|
self.state = VirtualMachineState::Prepared;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
while duration.map_or(true, |dur| (Instant::now() - start) < dur) {
|
while duration.map_or(true, |dur| (Instant::now() - start) < dur) {
|
||||||
let has_socket = self.control_socket.as_mut()
|
let has_socket = self
|
||||||
|
.control_socket
|
||||||
|
.as_mut()
|
||||||
.map(|x| x.qmp.nop())
|
.map(|x| x.qmp.nop())
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.is_some();
|
.is_some();
|
||||||
|
@ -480,7 +520,10 @@ impl VirtualMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut command = Command::new("qemu-system-x86_64");
|
let mut command = Command::new("qemu-system-x86_64");
|
||||||
command.args(self.get_cmd_line().context("Failed to generate qemu command 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 = || {
|
||||||
|
@ -499,7 +542,7 @@ impl VirtualMachine {
|
||||||
unix_stream = UnixStream::connect(&qemu_control_socket);
|
unix_stream = UnixStream::connect(&qemu_control_socket);
|
||||||
|
|
||||||
if let Some(proc) = self.process.as_mut() {
|
if let Some(proc) = self.process.as_mut() {
|
||||||
if let Some(_) = proc.try_wait()? {
|
if proc.try_wait()?.is_some() {
|
||||||
anyhow::bail!("QEMU quit early")
|
anyhow::bail!("QEMU quit early")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -520,6 +563,18 @@ impl VirtualMachine {
|
||||||
|
|
||||||
self.pin_qemu_threads()?;
|
self.pin_qemu_threads()?;
|
||||||
|
|
||||||
|
if self.config.looking_glass.enabled {
|
||||||
|
self.global_config
|
||||||
|
.vore
|
||||||
|
.chown(&self.config.looking_glass.mem_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.config.spice.enabled {
|
||||||
|
self.global_config
|
||||||
|
.vore
|
||||||
|
.chown(&self.config.spice.socket_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
control_socket
|
control_socket
|
||||||
.qmp
|
.qmp
|
||||||
.execute(&qapi_qmp::cont {})
|
.execute(&qapi_qmp::cont {})
|
||||||
|
|
36
vore-core/src/virtual_machine_info.rs
Normal file
36
vore-core/src/virtual_machine_info.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::fmt;
|
||||||
|
use crate::InstanceConfig;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum VirtualMachineState {
|
||||||
|
Loaded,
|
||||||
|
Prepared,
|
||||||
|
Stopped,
|
||||||
|
Paused,
|
||||||
|
Running,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for VirtualMachineState {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
VirtualMachineState::Loaded => write!(f, "loaded"),
|
||||||
|
VirtualMachineState::Prepared => write!(f, "prepared"),
|
||||||
|
VirtualMachineState::Stopped => write!(f, "stopped"),
|
||||||
|
VirtualMachineState::Paused => write!(f, "paused"),
|
||||||
|
VirtualMachineState::Running => write!(f, "running")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct VirtualMachineInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub working_dir: PathBuf,
|
||||||
|
pub config: InstanceConfig,
|
||||||
|
pub state: VirtualMachineState,
|
||||||
|
pub quit_after_shutdown: bool,
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ args:
|
||||||
help: "Connect to the specified socket"
|
help: "Connect to the specified socket"
|
||||||
required: false
|
required: false
|
||||||
takes_value: true
|
takes_value: true
|
||||||
default_value: "/run/vore.sock"
|
|
||||||
long: conn
|
long: conn
|
||||||
short: c
|
short: c
|
||||||
|
|
||||||
|
@ -102,6 +101,7 @@ subcommands:
|
||||||
visible_alias: lg
|
visible_alias: lg
|
||||||
args:
|
args:
|
||||||
- vm-name:
|
- vm-name:
|
||||||
|
long: vm
|
||||||
help: "VM to start looking glass instance for, if not given the ONLY running instance will be used"
|
help: "VM to start looking glass instance for, if not given the ONLY running instance will be used"
|
||||||
required: false
|
required: false
|
||||||
takes_value: true
|
takes_value: true
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
mod client;
|
mod client;
|
||||||
|
|
||||||
use vore_core::{init_logging, VirtualMachineInfo};
|
|
||||||
use crate::client::Client;
|
use crate::client::Client;
|
||||||
use clap::{App, ArgMatches};
|
|
||||||
use std::{fs, mem};
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use clap::{App, ArgMatches};
|
||||||
use std::option::Option::Some;
|
use std::option::Option::Some;
|
||||||
use std::process::Command;
|
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::{fs, mem};
|
||||||
|
use vore_core::consts::VORE_SOCKET;
|
||||||
|
use vore_core::{init_logging, VirtualMachineInfo};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
init_logging();
|
init_logging();
|
||||||
|
@ -21,11 +22,9 @@ fn main_res() -> anyhow::Result<()> {
|
||||||
let yaml = clap::load_yaml!("../clap.yml");
|
let yaml = clap::load_yaml!("../clap.yml");
|
||||||
let app: App = App::from(yaml);
|
let app: App = App::from(yaml);
|
||||||
let matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
let client = Client::connect(matches.value_of("vored-socket").unwrap())?;
|
let client = Client::connect(matches.value_of("vored-socket").unwrap_or(VORE_SOCKET))?;
|
||||||
|
|
||||||
let mut vore = VoreApp {
|
let mut vore = VoreApp { client };
|
||||||
client
|
|
||||||
};
|
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
("load", Some(args)) => {
|
("load", Some(args)) => {
|
||||||
|
@ -52,8 +51,7 @@ fn main_res() -> anyhow::Result<()> {
|
||||||
vore.looking_glass(args)?;
|
vore.looking_glass(args)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
("daemon", Some(args)) => {
|
("daemon", Some(args)) => match args.subcommand() {
|
||||||
match args.subcommand() {
|
|
||||||
("version", _) => {
|
("version", _) => {
|
||||||
vore.daemon_version()?;
|
vore.daemon_version()?;
|
||||||
}
|
}
|
||||||
|
@ -61,8 +59,7 @@ fn main_res() -> anyhow::Result<()> {
|
||||||
(s, _) => {
|
(s, _) => {
|
||||||
log::error!("Subcommand daemon.{} not implemented", s);
|
log::error!("Subcommand daemon.{} not implemented", s);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
(s, _) => {
|
(s, _) => {
|
||||||
log::error!("Subcommand {} not implemented", s);
|
log::error!("Subcommand {} not implemented", s);
|
||||||
|
@ -72,20 +69,22 @@ fn main_res() -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LoadVMOptions {
|
struct LoadVirtualMachineOptions {
|
||||||
config: String,
|
config: String,
|
||||||
cd_roms: Vec<String>,
|
cd_roms: Vec<String>,
|
||||||
save: bool,
|
save: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_load_vm_options(args: &ArgMatches) -> anyhow::Result<LoadVMOptions> {
|
fn get_load_vm_options(args: &ArgMatches) -> anyhow::Result<LoadVirtualMachineOptions> {
|
||||||
let vm_config_path = args.value_of("vm-config").unwrap();
|
let vm_config_path = args.value_of("vm-config").unwrap();
|
||||||
let config = fs::read_to_string(vm_config_path)
|
let config = fs::read_to_string(vm_config_path)
|
||||||
.with_context(|| format!("Failed to read vm config at {}", vm_config_path))?;
|
.with_context(|| format!("Failed to read vm config at {}", vm_config_path))?;
|
||||||
|
|
||||||
Ok(LoadVMOptions {
|
Ok(LoadVirtualMachineOptions {
|
||||||
config,
|
config,
|
||||||
cd_roms: 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"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -102,11 +101,13 @@ impl VoreApp {
|
||||||
pub fn get_vm(&mut self, args: &ArgMatches) -> anyhow::Result<VirtualMachineInfo> {
|
pub fn get_vm(&mut self, args: &ArgMatches) -> anyhow::Result<VirtualMachineInfo> {
|
||||||
let mut items = self.client.list_vms()?;
|
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") {
|
||||||
items.into_iter().find(|x| x.name == vm_name)
|
items
|
||||||
|
.into_iter()
|
||||||
|
.find(|x| x.name == vm_name)
|
||||||
.with_context(|| format!("Couldn't find VM with the name '{}'", vm_name))
|
.with_context(|| format!("Couldn't find VM with the name '{}'", vm_name))
|
||||||
} else {
|
} else {
|
||||||
match (items.len(), items.pop()) {
|
match (items.len(), items.pop()) {
|
||||||
(amount, Some(x)) if amount == 1 => return Ok(x),
|
(amount, Some(x)) if amount == 1 => 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"),
|
||||||
}
|
}
|
||||||
|
@ -122,7 +123,9 @@ 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.cd_roms)?;
|
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(())
|
||||||
}
|
}
|
||||||
|
@ -139,13 +142,21 @@ impl VoreApp {
|
||||||
|
|
||||||
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(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<()> {
|
fn start(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
let name = self.get_vm_name(args)?;
|
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<_>>()))?;
|
self.client.start(
|
||||||
|
name,
|
||||||
|
args.values_of("cdrom")
|
||||||
|
.map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()),
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +166,9 @@ impl VoreApp {
|
||||||
anyhow::bail!("VM '{}' has no looking glass", vm.name);
|
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()));
|
let mut command = Command::new(
|
||||||
|
std::env::var("LOOKING_GLASS").unwrap_or_else(|_| "looking-glass-client".to_string()),
|
||||||
|
);
|
||||||
if vm.config.spice.enabled {
|
if vm.config.spice.enabled {
|
||||||
command.args(&["-c", &vm.config.spice.socket_path, "-p", "0"]);
|
command.args(&["-c", &vm.config.spice.socket_path, "-p", "0"]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -163,7 +176,10 @@ impl VoreApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
command.args(&["-f", &vm.config.looking_glass.mem_path]);
|
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<_>>()));
|
command.args(
|
||||||
|
args.values_of("looking-glass-args")
|
||||||
|
.map_or(vec![], |x| x.into_iter().collect::<Vec<_>>()),
|
||||||
|
);
|
||||||
|
|
||||||
mem::drop(self);
|
mem::drop(self);
|
||||||
command.exec();
|
command.exec();
|
||||||
|
|
|
@ -8,7 +8,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.40"
|
anyhow = "1.0.40"
|
||||||
vore-core = { path = "../vore-core" }
|
vore-core = { path = "../vore-core", features = ["host"] }
|
||||||
polling = "2.0.3"
|
polling = "2.0.3"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
pretty_env_logger = "0.3"
|
pretty_env_logger = "0.3"
|
||||||
|
|
|
@ -1,24 +1,27 @@
|
||||||
use std::collections::{HashMap};
|
|
||||||
use vore_core::{VirtualMachine, InstanceConfig, GlobalConfig, GLOBAL_CONFIG_LOCATION};
|
|
||||||
use std::os::unix::net::{UnixListener, SocketAddr, UnixStream};
|
|
||||||
use polling::{Poller, Event};
|
|
||||||
use std::time::Duration;
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use std::{mem, io};
|
use polling::{Event, Poller};
|
||||||
use std::io::{Read, Write};
|
use signal_hook::consts::{SIGHUP, SIGINT, SIGTERM};
|
||||||
use vore_core::rpc::{CommandCenter, Response, Command, AllRequests, AllResponses};
|
use signal_hook::iterator::{Handle, Signals, SignalsInfo};
|
||||||
use vore_core::rpc;
|
use signal_hook::low_level::signal_name;
|
||||||
use signal_hook::low_level::{signal_name};
|
use std::collections::HashMap;
|
||||||
use signal_hook::consts::{SIGINT, SIGTERM, SIGHUP};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use signal_hook::iterator::{SignalsInfo, Signals, Handle};
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::{read_dir, read_to_string, DirEntry};
|
||||||
|
use std::io::{Read, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use std::os::unix::net::{SocketAddr, UnixListener, UnixStream};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{io, mem};
|
||||||
|
use vore_core::consts::{VORE_CONFIG, VORE_DIRECTORY, VORE_SOCKET};
|
||||||
|
use vore_core::rpc::{AllRequests, AllResponses, Command, CommandCenter, Response};
|
||||||
|
use vore_core::{rpc, VirtualMachineInfo};
|
||||||
|
use vore_core::{GlobalConfig, InstanceConfig, VirtualMachine};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct RPCConnection {
|
struct RpcConnection {
|
||||||
stream: UnixStream,
|
stream: UnixStream,
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
buffer: Vec<u8>,
|
buffer: Vec<u8>,
|
||||||
|
@ -27,7 +30,7 @@ struct RPCConnection {
|
||||||
pid: i32,
|
pid: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for RPCConnection {
|
impl Write for RpcConnection {
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
self.stream.write(buf)
|
self.stream.write(buf)
|
||||||
}
|
}
|
||||||
|
@ -37,16 +40,20 @@ impl Write for RPCConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for RPCConnection {
|
impl Read for RpcConnection {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
self.stream.read(buf)
|
self.stream.read(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::char_lit_as_u8)]
|
||||||
const NEWLINE: u8 = '\n' as u8;
|
const NEWLINE: u8 = '\n' as u8;
|
||||||
|
|
||||||
impl RPCConnection {
|
impl RpcConnection {
|
||||||
pub fn handle_input(&mut self, own_id: usize) -> Result<(bool, Vec<(usize, Command)>), anyhow::Error> {
|
pub fn handle_input(
|
||||||
|
&mut self,
|
||||||
|
own_id: usize,
|
||||||
|
) -> Result<(bool, Vec<(usize, Command)>), anyhow::Error> {
|
||||||
let mut still_open = true;
|
let mut still_open = true;
|
||||||
loop {
|
loop {
|
||||||
let mut buffer = vec![0u8; 4096];
|
let mut buffer = vec![0u8; 4096];
|
||||||
|
@ -57,13 +64,21 @@ impl RPCConnection {
|
||||||
}
|
}
|
||||||
Ok(amount) => self.buffer.extend_from_slice(&buffer[..amount]),
|
Ok(amount) => self.buffer.extend_from_slice(&buffer[..amount]),
|
||||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => break,
|
Err(err) if err.kind() == io::ErrorKind::WouldBlock => break,
|
||||||
Err(err) => return Err(err.into())
|
Err(err) => return Err(err.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buffer = mem::take(&mut self.buffer);
|
let mut buffer = mem::take(&mut self.buffer);
|
||||||
if still_open {
|
if still_open {
|
||||||
self.buffer = buffer.split_off(buffer.iter().enumerate().rev().find(|(_, x)| **x == NEWLINE).map(|(idx, _)| idx + 1).unwrap_or(buffer.len()));
|
self.buffer = buffer.split_off(
|
||||||
|
buffer
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.rev()
|
||||||
|
.find(|(_, x)| **x == NEWLINE)
|
||||||
|
.map(|(idx, _)| idx + 1)
|
||||||
|
.unwrap_or(buffer.len()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut commands = vec![];
|
let mut commands = vec![];
|
||||||
|
@ -73,7 +88,6 @@ impl RPCConnection {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let lossy = String::from_utf8_lossy(part);
|
let lossy = String::from_utf8_lossy(part);
|
||||||
|
|
||||||
match CommandCenter::read_command(&lossy) {
|
match CommandCenter::read_command(&lossy) {
|
||||||
|
@ -94,9 +108,9 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +119,7 @@ pub struct Daemon {
|
||||||
event_key_storage: Vec<EventTarget>,
|
event_key_storage: Vec<EventTarget>,
|
||||||
global_config: GlobalConfig,
|
global_config: GlobalConfig,
|
||||||
machines: HashMap<String, VirtualMachine>,
|
machines: HashMap<String, VirtualMachine>,
|
||||||
connections: Vec<Option<RPCConnection>>,
|
connections: Vec<Option<RpcConnection>>,
|
||||||
rpc_listener: UnixListener,
|
rpc_listener: UnixListener,
|
||||||
socket_path: PathBuf,
|
socket_path: PathBuf,
|
||||||
poller: Poller,
|
poller: Poller,
|
||||||
|
@ -117,18 +131,22 @@ pub struct Daemon {
|
||||||
|
|
||||||
impl Daemon {
|
impl Daemon {
|
||||||
pub fn new() -> Result<Daemon, anyhow::Error> {
|
pub fn new() -> Result<Daemon, anyhow::Error> {
|
||||||
log::debug!("Loading global config ({})", GLOBAL_CONFIG_LOCATION);
|
log::debug!("Loading global config ({})", VORE_CONFIG);
|
||||||
let toml = std::fs::read_to_string(GLOBAL_CONFIG_LOCATION)?;
|
let toml = std::fs::read_to_string(VORE_CONFIG)?;
|
||||||
let global_config = GlobalConfig::load(&toml)?;
|
let mut global_config = GlobalConfig::load(&toml)?;
|
||||||
log::debug!("Creating vore daemon");
|
log::debug!("Creating vore daemon");
|
||||||
let signals = Signals::new(&[SIGINT, SIGHUP])?;
|
let signals = Signals::new(&[SIGINT, SIGHUP])?;
|
||||||
let handle = signals.handle();
|
let handle = signals.handle();
|
||||||
log::debug!("Bound signal handlers");
|
log::debug!("Bound signal handlers");
|
||||||
let poller = Poller::new().context("Failed to make poller")?;
|
let poller = Poller::new().context("Failed to make poller")?;
|
||||||
let socket_path = PathBuf::from_str("/run/vore.sock")?;
|
let socket_path = PathBuf::from_str(VORE_SOCKET)?;
|
||||||
let rpc_listener = UnixListener::bind(&socket_path).context("Failed to bind vore socket")?;
|
let rpc_listener =
|
||||||
|
UnixListener::bind(&socket_path).context("Failed to bind vore socket")?;
|
||||||
|
|
||||||
|
global_config.vore.chown(socket_path.to_str().unwrap())?;
|
||||||
|
|
||||||
rpc_listener.set_nonblocking(true)?;
|
rpc_listener.set_nonblocking(true)?;
|
||||||
log::debug!("Bound to /run/vore.sock");
|
log::debug!("Bound to {}", VORE_SOCKET);
|
||||||
|
|
||||||
let mut daemon = Daemon {
|
let mut daemon = Daemon {
|
||||||
event_key_storage: vec![],
|
event_key_storage: vec![],
|
||||||
|
@ -149,23 +167,106 @@ impl Daemon {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self) -> Result<(), anyhow::Error> {
|
pub fn init(&mut self) -> Result<(), anyhow::Error> {
|
||||||
let new_key = self.add_target(EventTarget::RPCListener);
|
let new_key = self.add_target(EventTarget::RpcListener);
|
||||||
self.poller.add(&self.rpc_listener, Event::readable(new_key))?;
|
self.poller
|
||||||
|
.add(&self.rpc_listener, Event::readable(new_key))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_definitions(&mut self) -> Result<(), anyhow::Error> {
|
||||||
|
let vm_dir = PathBuf::from(format!("{}/definitions", VORE_DIRECTORY));
|
||||||
|
if !vm_dir.is_dir() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir_iter =
|
||||||
|
read_dir(&vm_dir).with_context(|| format!("Failed to list {:?} for vm's", &vm_dir))?;
|
||||||
|
|
||||||
|
let mut process = |entry: Result<DirEntry, io::Error>| -> anyhow::Result<()> {
|
||||||
|
let entry = entry?;
|
||||||
|
let file_name = entry.path();
|
||||||
|
let path = file_name.to_str().context("Entry has invalid UTF-8 path")?;
|
||||||
|
if !path.ends_with(".toml") {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let toml = read_to_string(path)
|
||||||
|
.with_context(|| format!("Failed to read VM definition {}", path))?;
|
||||||
|
self.load_virtual_machine(&toml, None, false)?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in dir_iter {
|
||||||
|
if let Err(err) = process(entry) {
|
||||||
|
log::error!("Failed parsing entry in {:?}: {:?}", vm_dir, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reserve_vfio_devices(&mut self) {
|
||||||
|
for machine in self.machines.values() {
|
||||||
|
for vfio_device in machine.vfio_devices() {
|
||||||
|
if !vfio_device.reserve {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = VirtualMachine::prepare_vfio_device(true, true, &vfio_device) {
|
||||||
|
log::error!(
|
||||||
|
"Failed to reserve PCI device {} for {}: {:?}",
|
||||||
|
vfio_device.address,
|
||||||
|
machine.name(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log::info!(
|
||||||
|
"Reserved PCI device {} for {}",
|
||||||
|
vfio_device.address,
|
||||||
|
machine.name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auto_start_machines(&mut self) {
|
||||||
|
for machine in self.machines.values_mut() {
|
||||||
|
if !machine.should_auto_start() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = machine.start() {
|
||||||
|
log::error!("Failed to auto-start {}: {:?}", machine.name(), err);
|
||||||
|
} else {
|
||||||
|
log::info!("Autostarted {}", machine.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<(), anyhow::Error> {
|
pub fn run(&mut self) -> Result<(), anyhow::Error> {
|
||||||
|
self.load_definitions()?;
|
||||||
|
self.reserve_vfio_devices();
|
||||||
|
self.auto_start_machines();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let res = self.wait().context("Got error while waiting for new notifications");
|
let res = self
|
||||||
|
.wait()
|
||||||
|
.context("Got error while waiting for new notifications");
|
||||||
match res {
|
match res {
|
||||||
// Interrupted is uh "always" when we get a signal
|
// Interrupted is uh "always" when we get a signal
|
||||||
Err(err) if err.downcast_ref::<io::Error>().map(|x| x.kind() == io::ErrorKind::Interrupted).unwrap_or(false) => {
|
Err(err)
|
||||||
|
if err
|
||||||
|
.downcast_ref::<io::Error>()
|
||||||
|
.map(|x| x.kind() == io::ErrorKind::Interrupted)
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
if !self.handle_exit_code()? {
|
if !self.handle_exit_code()? {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err => err?
|
err => err?,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.handle_event_queue()? {
|
if !self.handle_event_queue()? {
|
||||||
|
@ -196,37 +297,63 @@ impl Daemon {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_command(&mut self, command: &Command) -> Result<AllResponses, anyhow::Error> {
|
pub fn load_virtual_machine(
|
||||||
let resp = match &command.data {
|
&mut self,
|
||||||
AllRequests::Info(_) => {
|
toml: &str,
|
||||||
rpc::InfoResponse {
|
working_directory: Option<String>,
|
||||||
name: "vore".to_string(),
|
save: bool,
|
||||||
version: format!("{}.{}.{}{}",
|
) -> anyhow::Result<VirtualMachineInfo> {
|
||||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
let config = InstanceConfig::from_toml(&toml)?;
|
||||||
env!("CARGO_PKG_VERSION_MINOR"),
|
if save {
|
||||||
env!("CARGO_PKG_VERSION_PATCH"),
|
let save_file = format!("{}/definitions/{}.toml", VORE_DIRECTORY, config.name);
|
||||||
option_env!("CARGO_PKG_VERSION_PRE").unwrap_or("")),
|
let file_path = Path::new(&save_file);
|
||||||
|
if let Some(parent_dir) = file_path.parent() {
|
||||||
|
if !parent_dir.is_dir() {
|
||||||
|
fs::create_dir_all(parent_dir)?;
|
||||||
}
|
}
|
||||||
.into_enum()
|
|
||||||
}
|
}
|
||||||
AllRequests::List(_) => {
|
|
||||||
rpc::ListResponse {
|
fs::write(&save_file, toml).with_context(|| {
|
||||||
items: self.machines.values().map(|x| x.info()).collect()
|
format!(
|
||||||
|
"Failed to save vm definition for {} to {}",
|
||||||
|
config.name, save_file
|
||||||
|
)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
.into_enum()
|
|
||||||
}
|
let working_dir = working_directory
|
||||||
AllRequests::Load(val) => {
|
.unwrap_or_else(|| format!("{}/instance/{}", VORE_DIRECTORY, config.name));
|
||||||
let config = InstanceConfig::from_toml(&val.toml)?;
|
|
||||||
let working_dir = val.working_directory.as_ref().cloned().unwrap_or_else(|| format!("/var/lib/vore/{}", config.name));
|
|
||||||
let vm = VirtualMachine::new(config, &self.global_config, working_dir);
|
let vm = VirtualMachine::new(config, &self.global_config, working_dir);
|
||||||
let info = vm.info();
|
let info = vm.info();
|
||||||
self.mount_machine(vm);
|
self.mount_machine(vm);
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
|
||||||
rpc::LoadResponse {
|
pub fn handle_command(&mut self, command: &Command) -> Result<AllResponses, anyhow::Error> {
|
||||||
info,
|
let resp = match &command.data {
|
||||||
|
AllRequests::Info(_) => rpc::InfoResponse {
|
||||||
|
name: "vore".to_string(),
|
||||||
|
version: format!(
|
||||||
|
"{}.{}.{}{}",
|
||||||
|
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||||
|
env!("CARGO_PKG_VERSION_MINOR"),
|
||||||
|
env!("CARGO_PKG_VERSION_PATCH"),
|
||||||
|
option_env!("CARGO_PKG_VERSION_PRE").unwrap_or("")
|
||||||
|
),
|
||||||
}
|
}
|
||||||
.into_enum()
|
.into_enum(),
|
||||||
|
AllRequests::List(_) => rpc::ListResponse {
|
||||||
|
items: self.machines.values().map(|x| x.info()).collect(),
|
||||||
}
|
}
|
||||||
|
.into_enum(),
|
||||||
|
AllRequests::Load(val) => rpc::LoadResponse {
|
||||||
|
info: self.load_virtual_machine(
|
||||||
|
&val.toml,
|
||||||
|
val.working_directory.as_ref().cloned(),
|
||||||
|
val.save,
|
||||||
|
)?,
|
||||||
|
}
|
||||||
|
.into_enum(),
|
||||||
AllRequests::Prepare(val) => {
|
AllRequests::Prepare(val) => {
|
||||||
if let Some(machine) = self.machines.get_mut(&val.name) {
|
if let Some(machine) = self.machines.get_mut(&val.name) {
|
||||||
machine.prepare(true, false)?;
|
machine.prepare(true, false)?;
|
||||||
|
@ -280,7 +407,11 @@ impl Daemon {
|
||||||
|
|
||||||
pub fn handle_exit_code(&mut self) -> Result<bool, anyhow::Error> {
|
pub fn handle_exit_code(&mut self) -> Result<bool, anyhow::Error> {
|
||||||
for signal in self.signals.pending() {
|
for signal in self.signals.pending() {
|
||||||
log::info!("Received signal {} ({})", signal_name(signal).unwrap_or("<unknown>"), signal);
|
log::info!(
|
||||||
|
"Received signal {} ({})",
|
||||||
|
signal_name(signal).unwrap_or("<unknown>"),
|
||||||
|
signal
|
||||||
|
);
|
||||||
match signal {
|
match signal {
|
||||||
SIGINT | SIGTERM => return Ok(false),
|
SIGINT | SIGTERM => return Ok(false),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -297,8 +428,9 @@ impl Daemon {
|
||||||
log::debug!("Handling {:?} from target {:?}", event, item);
|
log::debug!("Handling {:?} from target {:?}", event, item);
|
||||||
|
|
||||||
match item {
|
match item {
|
||||||
EventTarget::RPCListener => {
|
EventTarget::RpcListener => {
|
||||||
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) => {
|
||||||
|
@ -306,15 +438,27 @@ impl Daemon {
|
||||||
machine.boop()?;
|
machine.boop()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(control_socket) = self.machines.get(&name).and_then(|x| x.control_stream()) {
|
if let Some(control_socket) =
|
||||||
self.poller.modify(control_socket, Event::readable(event.key))?;
|
self.machines.get(&name).and_then(|x| x.control_stream())
|
||||||
|
{
|
||||||
|
self.poller
|
||||||
|
.modify(control_socket, Event::readable(event.key))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventTarget::RPCConnection(rpc_connection_id) if self.connections.get(rpc_connection_id).map(Option::is_some).unwrap_or(false) => {
|
EventTarget::RpcConnection(rpc_connection_id)
|
||||||
let (still_open, mut commands) = if let Some(rpc_connection) = &mut self.connections[rpc_connection_id] {
|
if self
|
||||||
|
.connections
|
||||||
|
.get(rpc_connection_id)
|
||||||
|
.map(Option::is_some)
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
|
let (still_open, mut commands) = if let Some(rpc_connection) =
|
||||||
|
&mut self.connections[rpc_connection_id]
|
||||||
|
{
|
||||||
let input_res = rpc_connection.handle_input(rpc_connection_id)?;
|
let input_res = rpc_connection.handle_input(rpc_connection_id)?;
|
||||||
if input_res.0 {
|
if input_res.0 {
|
||||||
self.poller.modify(&rpc_connection.stream, Event::readable(event.key))?;
|
self.poller
|
||||||
|
.modify(&rpc_connection.stream, Event::readable(event.key))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
input_res
|
input_res
|
||||||
|
@ -342,7 +486,7 @@ impl Daemon {
|
||||||
let (stream, address) = match self.rpc_listener.accept() {
|
let (stream, address) = match self.rpc_listener.accept() {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(()),
|
Err(err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(()),
|
||||||
Err(err) => return Err(err)?
|
Err(err) => return Err(err.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
stream.set_nonblocking(true)?;
|
stream.set_nonblocking(true)?;
|
||||||
|
@ -351,16 +495,25 @@ impl Daemon {
|
||||||
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;
|
||||||
libc::getsockopt(stream.as_raw_fd(), libc::SOL_SOCKET, libc::SO_PEERCRED, (&mut ucred) as *mut _ as _, &mut length);
|
libc::getsockopt(
|
||||||
|
stream.as_raw_fd(),
|
||||||
|
libc::SOL_SOCKET,
|
||||||
|
libc::SO_PEERCRED,
|
||||||
|
(&mut ucred) as *mut _ as _,
|
||||||
|
&mut length,
|
||||||
|
);
|
||||||
let passwd = libc::getpwuid(ucred.uid);
|
let passwd = libc::getpwuid(ucred.uid);
|
||||||
if !passwd.is_null() {
|
if !passwd.is_null() {
|
||||||
user = CStr::from_ptr((*passwd).pw_name).to_str().ok().map(|x| x.to_string())
|
user = CStr::from_ptr((*passwd).pw_name)
|
||||||
|
.to_str()
|
||||||
|
.ok()
|
||||||
|
.map(|x| x.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
ucred
|
ucred
|
||||||
};
|
};
|
||||||
|
|
||||||
let conn = RPCConnection {
|
let conn = RpcConnection {
|
||||||
stream,
|
stream,
|
||||||
address,
|
address,
|
||||||
buffer: vec![],
|
buffer: vec![],
|
||||||
|
@ -371,24 +524,35 @@ impl Daemon {
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"Got new RPC connection from {} (pid: {}, socket: {:?})",
|
"Got new RPC connection from {} (pid: {}, socket: {:?})",
|
||||||
conn.user.as_ref().map_or_else(|| format!("uid:{}", conn.uid), |x| format!("{} ({})", x, conn.uid)),
|
conn.user.as_ref().map_or_else(
|
||||||
|
|| format!("uid:{}", conn.uid),
|
||||||
|
|x| format!("{} ({})", x, conn.uid),
|
||||||
|
),
|
||||||
conn.pid,
|
conn.pid,
|
||||||
conn.address,
|
conn.address,
|
||||||
);
|
);
|
||||||
|
|
||||||
let id = self.add_rpc_connection(conn);
|
let id = self.add_rpc_connection(conn);
|
||||||
let event_target = self.add_target(EventTarget::RPCConnection(id));
|
let event_target = self.add_target(EventTarget::RpcConnection(id));
|
||||||
self.poller.add(&self.connections[id].as_ref().unwrap().stream, Event::readable(event_target))?;
|
self.poller.add(
|
||||||
|
&self.connections[id].as_ref().unwrap().stream,
|
||||||
|
Event::readable(event_target),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(&mut self) -> Result<(), anyhow::Error> {
|
pub fn wait(&mut self) -> Result<(), anyhow::Error> {
|
||||||
self.poller.wait(&mut self.queue, Some(Duration::from_secs(5)))?;
|
self.poller
|
||||||
|
.wait(&mut self.queue, Some(Duration::from_secs(5)))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_target(&mut self, event_target: EventTarget) -> usize {
|
fn add_target(&mut self, event_target: EventTarget) -> usize {
|
||||||
let id = self.event_key_storage.iter().enumerate().find(|(_, target)| target.eq(&&EventTarget::None));
|
let id = self
|
||||||
|
.event_key_storage
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, target)| target.eq(&&EventTarget::None));
|
||||||
if let Some((id, _)) = id {
|
if let Some((id, _)) = id {
|
||||||
self.event_key_storage[id] = event_target;
|
self.event_key_storage[id] = event_target;
|
||||||
return id;
|
return id;
|
||||||
|
@ -396,11 +560,15 @@ impl Daemon {
|
||||||
|
|
||||||
let new_id = self.event_key_storage.len();
|
let new_id = self.event_key_storage.len();
|
||||||
self.event_key_storage.push(event_target);
|
self.event_key_storage.push(event_target);
|
||||||
return new_id;
|
new_id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_rpc_connection(&mut self, rpc_connection: RPCConnection) -> usize {
|
fn add_rpc_connection(&mut self, rpc_connection: RpcConnection) -> usize {
|
||||||
let id = self.connections.iter().enumerate().find(|(_, target)| target.is_none());
|
let id = self
|
||||||
|
.connections
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, target)| target.is_none());
|
||||||
if let Some((id, _)) = id {
|
if let Some((id, _)) = id {
|
||||||
self.connections[id] = Some(rpc_connection);
|
self.connections[id] = Some(rpc_connection);
|
||||||
return id;
|
return id;
|
||||||
|
@ -408,11 +576,12 @@ impl Daemon {
|
||||||
|
|
||||||
let new_id = self.connections.len();
|
let new_id = self.connections.len();
|
||||||
self.connections.push(Some(rpc_connection));
|
self.connections.push(Some(rpc_connection));
|
||||||
return new_id;
|
new_id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_machine(&mut self, vm: VirtualMachine) {
|
fn mount_machine(&mut self, vm: VirtualMachine) {
|
||||||
|
log::info!("Loaded {}", vm.name());
|
||||||
let name = vm.name().to_string();
|
let name = vm.name().to_string();
|
||||||
self.machines.insert(name.clone(), vm);
|
self.machines.insert(name, vm);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue