You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
7.8 KiB
Rust

use anyhow::{Context, Error};
use config::{Config, File, FileFormat, Value};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct InstanceConfig {
pub name: String,
pub kvm: bool,
pub memory: u64,
pub cpu: CpuConfig,
pub disks: Vec<DiskConfig>,
pub uefi: UefiConfig,
pub looking_glass: LookingGlassConfig,
pub scream: ScreamConfig,
}
impl InstanceConfig {
pub fn from_toml(toml: &str) -> Result<Self, anyhow::Error> {
let toml = Config::new().with_merged(File::from_str(toml, FileFormat::Toml))?;
Self::from_config(toml)
}
pub fn from_config(config: Config) -> Result<InstanceConfig, anyhow::Error> {
let mut instance_config = InstanceConfig::default();
if let Ok(name) = config.get_str("machine.name") {
instance_config.name = name
}
if let Ok(kvm) = config.get::<Value>("machine.kvm") {
instance_config.kvm = kvm.into_bool().context("machine.kvm should be a boolean")?;
}
if let Ok(mem) = config.get::<Value>("machine.memory") {
let mem = mem
.into_str()
.context("machine.memory should be a string or number")?;
instance_config.memory = parse_size(&mem)?;
}
if let Ok(cpu) = config.get_table("cpu") {
instance_config.cpu.apply_table(cpu)?
}
if let Ok(disks) = config.get::<Value>("disk") {
let arr = disks.into_array().context("disk should be an array")?;
for (i, disk) in arr.into_iter().enumerate() {
let table = disk
.into_table()
.with_context(|| format!("disk[{}] should be a table", i))?;
instance_config.disks.push(DiskConfig::from_table(table)?);
}
}
Ok(instance_config)
}
}
impl Default for InstanceConfig {
fn default() -> Self {
InstanceConfig {
name: "vore".to_string(),
kvm: true,
// 2 GB
memory: 2 * 1024 * 1024 * 1024,
cpu: Default::default(),
disks: vec![],
uefi: Default::default(),
looking_glass: Default::default(),
scream: Default::default(),
}
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct CpuConfig {
pub amount: u64,
pub cores: u64,
pub threads: u64,
pub dies: u64,
pub sockets: u64,
}
impl Default for CpuConfig {
fn default() -> Self {
CpuConfig {
amount: 2,
cores: 1,
threads: 2,
dies: 1,
sockets: 1,
}
}
}
fn get_positive_number_from_table(
table: &HashMap<String, Value>,
key: &str,
prefix: &str,
) -> Result<Option<u64>, Error> {
table
.get(key)
.cloned()
.map(|x| {
x.into_int()
.with_context(|| format!("Failed to parse {}.{} as number", prefix, key))
.and_then(|x| {
Some(x)
.filter(|x| !x.is_negative())
.map(|x| x as u64)
.ok_or_else(|| {
anyhow::Error::msg(format!("{}.{} can't be negative", prefix, key))
})
})
})
.transpose()
}
impl CpuConfig {
fn apply_table(&mut self, table: HashMap<String, Value>) -> Result<(), anyhow::Error> {
if let Some(amount) = get_positive_number_from_table(&table, "amount", "cpu")? {
self.amount = amount;
}
if let Some(cores) = get_positive_number_from_table(&table, "cores", "cpu")? {
self.cores = cores;
}
if let Some(threads) = get_positive_number_from_table(&table, "threads", "cpu")? {
self.threads = threads;
}
if let Some(dies) = get_positive_number_from_table(&table, "dies", "cpu")? {
self.dies = dies;
}
if let Some(sockets) = get_positive_number_from_table(&table, "sockets", "cpu")? {
self.sockets = sockets;
}
if !table.contains_key("amount") {
self.amount = self.sockets * self.dies * self.cores * self.threads;
} else {
if table
.keys()
.any(|x| ["cores", "sockets", "dies", "threads"].contains(&x.as_str()))
{
let calc_amount = self.sockets * self.dies * self.cores * self.threads;
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)))?;
}
} else {
if (self.amount % 2) == 0 {
self.cores = self.amount / 2;
} else {
self.threads = 1;
self.cores = self.amount;
}
}
}
Ok(())
}
}
fn parse_size(orig_input: &str) -> Result<u64, anyhow::Error> {
let input = orig_input.to_string().to_lowercase().replace(" ", "");
let mut input = input.strip_suffix("b").unwrap_or(&input);
let mut modifier: u64 = 1;
if input.chars().last().unwrap_or('_').is_alphabetic() {
modifier = match input.chars().last().unwrap() {
'k' => {
return Err(anyhow::Error::msg(
"size can only be specified in megabytes or larger",
));
}
'm' => 1,
'g' => 1024,
't' => 1024 * 1024,
_ => {
return Err(anyhow::Error::msg(format!(
"'{}' is not a valid size",
orig_input
)));
}
};
input = &input[..input.len() - 1];
}
if input.len() == 0 {
return Err(anyhow::Error::msg(format!(
"'{}' is not a valid size",
orig_input
)));
}
u64::from_str(input)
.context(format!("'{}' is not a valid size", orig_input))
.map(|x| x * modifier)
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct UefiConfig {
pub enabled: bool,
}
impl Default for UefiConfig {
fn default() -> Self {
UefiConfig { enabled: false }
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ScreamConfig {
pub enabled: bool,
}
impl Default for ScreamConfig {
fn default() -> Self {
ScreamConfig { enabled: false }
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct LookingGlassConfig {
pub enabled: bool,
}
impl Default for LookingGlassConfig {
fn default() -> Self {
LookingGlassConfig { enabled: false }
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct DiskConfig {
pub disk_type: String,
pub path: String,
}
impl DiskConfig {
pub fn from_table(table: HashMap<String, Value>) -> Result<DiskConfig, anyhow::Error> {
let path = table
.get("path")
.cloned()
.ok_or_else(|| anyhow::Error::msg("Disk needs a path"))?
.into_str()
.context("Disk path must be a string")?;
let disk_type = if let Some(disk_type) = table.get("type").cloned() {
disk_type.into_str()?
} else {
(kiam::when! {
path.starts_with("/dev") => "raw",
path.ends_with(".qcow2") => "qcow2",
_ => return Err(anyhow::Error::msg("Can't figure out from path what type of disk driver should be used"))
}).to_string()
};
let disk = DiskConfig { disk_type, path };
// TODO: Add blockdev details
Ok(disk)
}
}