added multiple new capabilities for daemon
- systemctl integration (reload, start, stop, enable, disable) - update vm capability - delete vm capability - save resources to disk - save VMs to disk - load commands from folders (new vm, update vm, delete vm) - fixed port forwarding
This commit is contained in:
parent
82273f63e5
commit
b85feef0ab
13
prod_setting/8443_and_ipv6.yaml
Normal file
13
prod_setting/8443_and_ipv6.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
uuid: "uuid-0001"
|
||||
hostname: "ghe-vm-1"
|
||||
admin_key: "MCowBQYDK2VwAyEAEoJ50VwJc7noWxylhioU2kk35MkO5as4U92UbP2A7xk="
|
||||
extra_ports: [80]
|
||||
public_ipv4: false
|
||||
public_ipv6: true
|
||||
disk_size_gb: 20
|
||||
vcpus: 2
|
||||
memory_mb: 2000
|
||||
kernel_url: "https://drive.google.com/uc?export=download&id=1bc2CmJjIBFSXRxTFQJy11uSobsazTs4n"
|
||||
kernel_sha: "203352667d403b437c856bec16809604e801e4f0cfabbf938daa8fda2fad0f84"
|
||||
dtrfs_url: "https://drive.google.com/uc?export=download&id=1WoAVb9VS0rlzmuEIwwzZMx7N7SsUQf6q"
|
||||
dtrfs_sha: "9fe9ae795a239a426a290f21dfa857182e3fe2fa556f8e8997ab35dfeb8fca24"
|
@ -1,5 +1,5 @@
|
||||
uuid: "0000000000000000000-0001"
|
||||
hostname: "ghe-vm-1"
|
||||
uuid: "uuid-0002"
|
||||
hostname: "ghe-vm-2"
|
||||
admin_key: "MCowBQYDK2VwAyEAEoJ50VwJc7noWxylhioU2kk35MkO5as4U92UbP2A7xk="
|
||||
extra_ports: []
|
||||
public_ipv4: true
|
||||
@ -9,5 +9,5 @@ vcpus: 2
|
||||
memory_mb: 2000
|
||||
kernel_url: "https://drive.google.com/uc?export=download&id=1bc2CmJjIBFSXRxTFQJy11uSobsazTs4n"
|
||||
kernel_sha: "203352667d403b437c856bec16809604e801e4f0cfabbf938daa8fda2fad0f84"
|
||||
dtrfs_url: "https://drive.google.com/uc?export=download&id=1r44_g-mi1PlFu5lmhN1kbuZdJgATINsg"
|
||||
dtrfs_sha: "f347574bb637c306aaf0f8d0650efc90b95cdc61ea6c1c0f5b87b7d162aae4f3"
|
||||
dtrfs_url: "https://drive.google.com/uc?export=download&id=1WoAVb9VS0rlzmuEIwwzZMx7N7SsUQf6q"
|
||||
dtrfs_sha: "9fe9ae795a239a426a290f21dfa857182e3fe2fa556f8e8997ab35dfeb8fca24"
|
||||
|
13
prod_setting/minimal_nat.yaml
Normal file
13
prod_setting/minimal_nat.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
uuid: "uuid-0004"
|
||||
hostname: "ghe-vm-4"
|
||||
admin_key: "MCowBQYDK2VwAyEAEoJ50VwJc7noWxylhioU2kk35MkO5as4U92UbP2A7xk="
|
||||
extra_ports: []
|
||||
public_ipv4: false
|
||||
public_ipv6: false
|
||||
disk_size_gb: 20
|
||||
vcpus: 2
|
||||
memory_mb: 2000
|
||||
kernel_url: "https://drive.google.com/uc?export=download&id=1bc2CmJjIBFSXRxTFQJy11uSobsazTs4n"
|
||||
kernel_sha: "203352667d403b437c856bec16809604e801e4f0cfabbf938daa8fda2fad0f84"
|
||||
dtrfs_url: "https://drive.google.com/uc?export=download&id=1WoAVb9VS0rlzmuEIwwzZMx7N7SsUQf6q"
|
||||
dtrfs_sha: "9fe9ae795a239a426a290f21dfa857182e3fe2fa556f8e8997ab35dfeb8fca24"
|
13
prod_setting/only_ipv4.yaml
Normal file
13
prod_setting/only_ipv4.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
uuid: "uuid-0003"
|
||||
hostname: "ghe-vm-3"
|
||||
admin_key: "MCowBQYDK2VwAyEAEoJ50VwJc7noWxylhioU2kk35MkO5as4U92UbP2A7xk="
|
||||
extra_ports: []
|
||||
public_ipv4: true
|
||||
public_ipv6: false
|
||||
disk_size_gb: 20
|
||||
vcpus: 2
|
||||
memory_mb: 2000
|
||||
kernel_url: "https://drive.google.com/uc?export=download&id=1bc2CmJjIBFSXRxTFQJy11uSobsazTs4n"
|
||||
kernel_sha: "203352667d403b437c856bec16809604e801e4f0cfabbf938daa8fda2fad0f84"
|
||||
dtrfs_url: "https://drive.google.com/uc?export=download&id=1WoAVb9VS0rlzmuEIwwzZMx7N7SsUQf6q"
|
||||
dtrfs_sha: "9fe9ae795a239a426a290f21dfa857182e3fe2fa556f8e8997ab35dfeb8fca24"
|
@ -4,7 +4,7 @@
|
||||
echo "Environment variable VM_UUID is not set."
|
||||
exit 1
|
||||
}
|
||||
source "/etc/detee/daemon/vms/$VM_UUID"
|
||||
source "/etc/detee/daemon/vms/${VM_UUID}.sh"
|
||||
|
||||
mandatory_vars=("KERNEL" "INITRD" "PARAMS" "CPU_TYPE" \
|
||||
"VCPUS" "MEMORY" "MAX_MEMORY" "DISK")
|
||||
@ -15,9 +15,10 @@ for var in "${mandatory_vars[@]}"; do
|
||||
fi
|
||||
done
|
||||
|
||||
interfaces=$(env | grep -oE '^NETWORK_INTERFACE_[0-9]*')
|
||||
interfaces=$(env | sort | grep -oE '^NETWORK_INTERFACE_[0-9]*')
|
||||
nat_configured="false"
|
||||
vtap_fd_counter=3
|
||||
vtap_nic_count=1
|
||||
qemu_device_params=""
|
||||
while read -r interface; do
|
||||
|
||||
interface_type="$( echo ${!interface} | cut -d '_' -f1 )"
|
||||
@ -28,38 +29,37 @@ while read -r interface; do
|
||||
if [[ "$interface_type" == "macvtap" ]]; then
|
||||
ip link add link $interface_device name $interface_name type $interface_type mode bridge
|
||||
else
|
||||
ip link add link $interface_device name $interface_name type $interface_type
|
||||
ip link add link $interface_device name $interface_name type $interface_type mode l3
|
||||
fi
|
||||
sysctl -w net.ipv6.conf.$interface_name.accept_ra=0
|
||||
ip link set $interface_name up
|
||||
ip link set $interface_name promisc on
|
||||
|
||||
vtap_index="$(cat /sys/class/net/${interface_name}/ifindex)"
|
||||
vtap_addr="$(cat /sys/class/net/${interface_name}/address)"
|
||||
|
||||
fd_number=$vtap_fd_counter
|
||||
exec {fd_number}<> /dev/tap${vtap_index}
|
||||
qemu_device_params="-netdev tap,id=hostnet1,fd=${fd_number} -device virtio-net-pci,netdev=hostnet1,mac=${vtap_addr},romfile="
|
||||
((vtap_fd_counter++))
|
||||
qemu_device_params+=" -netdev tap,id=hostnet1,fd=${fd_number}"
|
||||
qemu_device_params+=" -device virtio-net-pci,netdev=hostnet${vtap_nic_count},mac=${vtap_addr},romfile="
|
||||
((vtap_nic_count++))
|
||||
fi
|
||||
|
||||
if [[ "$interface_type" == "NAT" && "$nat_configured" == "false" ]]; then
|
||||
ports=""
|
||||
nat_configured="true"
|
||||
for port_pair in "$NAT_PORT_FW"; do
|
||||
for port_pair in $NAT_PORT_FW; do
|
||||
host_port="$( echo $port_pair | cut -d ':' -f1 )"
|
||||
guest_port="$( echo $port_pair | cut -d ':' -f2 )"
|
||||
ports+=",hostfwd=tcp::${host_port}-:${guest_port}"
|
||||
done
|
||||
qemu_device_params="-netdev user,id=vmnic${ports}"
|
||||
qemu_device_params+=" -device virtio-net-pci,netdev=vmnic,romfile="
|
||||
qemu_device_params+=" -netdev user,id=natnic${ports}"
|
||||
qemu_device_params+=" -device virtio-net-pci,netdev=natnic,romfile="
|
||||
fi
|
||||
|
||||
# TODO: also handle bridge device (when IPs are public, but the host is the gateway)
|
||||
|
||||
done <<< "$( echo "$interfaces" )"
|
||||
|
||||
vm_disk="/root/dtrfs/arch-1-ghe0.qcow2"
|
||||
|
||||
qemu-system-x86_64 $qemu_device_params \
|
||||
-enable-kvm -cpu $CPU_TYPE -vga none \
|
||||
-machine q35,confidential-guest-support=sev0,memory-backend=ram1 \
|
||||
|
@ -5,7 +5,7 @@ use std::collections::HashSet;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Volume {
|
||||
pub path: String,
|
||||
pub max_reservation_gb: usize,
|
||||
@ -83,7 +83,7 @@ mod range_format {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
pub fn load_from_disk(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let config: Config = serde_yaml::from_str(&content)?;
|
||||
Ok(config)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
pub(crate) const DEFAULT_OVMF: &str = "/usr/share/edk2/ovmf/OVMF.amdsev.fd";
|
||||
pub(crate) const VM_BOOT_DIR: &str = "/var/lib/detee/boot/";
|
||||
pub(crate) const USED_RESOURCES: &str = "/etc/detee/daemon/used_resources.yaml";
|
||||
pub(crate) const VM_CONFIG_DIR: &str = "/etc/detee/daemon/vms/";
|
||||
pub(crate) const DAEMON_CONFIG_PATH: &str = "/etc/detee/daemon/config.yaml";
|
||||
pub(crate) const START_VM_SCRIPT: &str = "/usr/local/bin/detee/start_qemu_vm.sh";
|
||||
|
56
src/main.rs
56
src/main.rs
@ -1,18 +1,60 @@
|
||||
mod config;
|
||||
mod state;
|
||||
mod constants;
|
||||
mod state;
|
||||
mod tcontract;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::state::NewVMRequest;
|
||||
use crate::state::UpdateVMReq;
|
||||
use std::fs::read_dir;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let config = Config::from_file(crate::constants::DAEMON_CONFIG_PATH)?;
|
||||
let mut res = state::Resources::new(&config.volumes);
|
||||
let new_vm_req = NewVMRequest::from_file(&args[1])?;
|
||||
let vm = state::VM::new(new_vm_req, &config, &mut res);
|
||||
println!("Got VM: {:#?}", vm);
|
||||
println!("Starting VM... \n{:?}", vm.unwrap().start());
|
||||
let config = Config::load_from_disk(crate::constants::DAEMON_CONFIG_PATH)?;
|
||||
let mut res = match state::Resources::load_from_disk() {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
println!("Could not load resources from disk: {e:?}");
|
||||
println!("Creating new resource calculator.");
|
||||
state::Resources::new(&config.volumes)
|
||||
}
|
||||
};
|
||||
|
||||
for entry in read_dir("/etc/detee/daemon/newvmreq/")? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let new_vm_req = NewVMRequest::from_file(path.to_str().unwrap())?;
|
||||
let vm = state::VM::new(new_vm_req, &config, &mut res).unwrap();
|
||||
vm.start()?;
|
||||
println!("started vm {}", vm.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
for entry in read_dir("/etc/detee/daemon/deletevm/")? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let vm_id = path.file_name().unwrap().to_str().unwrap();
|
||||
let content = std::fs::read_to_string(crate::constants::VM_CONFIG_DIR.to_string() + vm_id + ".yaml")?;
|
||||
let vm: crate::state::VM = serde_yaml::from_str(&content)?;
|
||||
vm.delete(&mut res)?;
|
||||
println!("deleted vm {}", vm.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
for entry in read_dir("/etc/detee/daemon/updatedvmreq/")? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let new_vm_req = UpdateVMReq::from_file(&args[1])?;
|
||||
let content = std::fs::read_to_string(crate::constants::VM_CONFIG_DIR.to_string() + &new_vm_req.uuid + ".yaml")?;
|
||||
let mut vm: crate::state::VM = serde_yaml::from_str(&content)?;
|
||||
vm.update(new_vm_req, &config, &mut res).unwrap();
|
||||
println!("updated vm {}", vm.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
354
src/state.rs
354
src/state.rs
@ -4,7 +4,9 @@ use crate::constants::*;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::fs::remove_file;
|
||||
@ -15,13 +17,14 @@ use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Resources {
|
||||
existing_vms: HashSet<String>,
|
||||
// QEMU does not support MHz limiation
|
||||
reserved_vcpus: usize,
|
||||
reserved_memory: usize,
|
||||
reserved_ports: HashSet<u16>,
|
||||
storage_pools: Vec<StoragePool>,
|
||||
reserved_storage: HashMap<String, usize>,
|
||||
reserved_ips: HashSet<String>,
|
||||
reserved_if_names: HashSet<String>,
|
||||
// sha256sum -> absolute path
|
||||
@ -29,6 +32,19 @@ pub struct Resources {
|
||||
}
|
||||
|
||||
impl Resources {
|
||||
fn save_to_disk(&self) -> Result<()> {
|
||||
let mut file = File::create(USED_RESOURCES)?;
|
||||
file.write_all(serde_yaml::to_string(self)?.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_from_disk() -> Result<Self> {
|
||||
let content = std::fs::read_to_string(USED_RESOURCES)?;
|
||||
let mut res: Self = serde_yaml::from_str(&content)?;
|
||||
res.scan_boot_files().unwrap();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn new(config_volumes: &Vec<crate::config::Volume>) -> Self {
|
||||
let mut storage_pools = Vec::new();
|
||||
for config_vol in config_volumes.iter() {
|
||||
@ -39,23 +55,32 @@ impl Resources {
|
||||
});
|
||||
}
|
||||
let mut res = Resources {
|
||||
existing_vms: HashSet::new(),
|
||||
reserved_vcpus: 0,
|
||||
reserved_memory: 0,
|
||||
reserved_ports: HashSet::new(),
|
||||
storage_pools,
|
||||
reserved_storage: HashMap::new(),
|
||||
reserved_ips: HashSet::new(),
|
||||
reserved_if_names: HashSet::new(),
|
||||
boot_files: HashSet::new(),
|
||||
};
|
||||
res.scan_boot_files().unwrap();
|
||||
let _ = res.save_to_disk();
|
||||
res
|
||||
}
|
||||
|
||||
fn available_storage_pool(&mut self, required_gb: usize) -> Option<String> {
|
||||
self.storage_pools.sort_by_key(|p| p.available_gb);
|
||||
let pool = self.storage_pools.last()?;
|
||||
if pool.available_gb > required_gb {
|
||||
return Some(pool.path.clone());
|
||||
fn available_storage_pool(&mut self, required_gb: usize, config: &Config) -> Option<String> {
|
||||
let mut volumes = config.volumes.clone();
|
||||
for volume in volumes.iter_mut() {
|
||||
if let Some(reservation) = self.reserved_storage.get(&volume.path) {
|
||||
volume.max_reservation_gb -= reservation;
|
||||
}
|
||||
}
|
||||
volumes.sort_by_key(|v| v.max_reservation_gb);
|
||||
if let Some(biggest_volume) = volumes.last() {
|
||||
if biggest_volume.max_reservation_gb > required_gb {
|
||||
return Some(biggest_volume.path.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@ -133,7 +158,6 @@ impl Resources {
|
||||
None
|
||||
}
|
||||
|
||||
// TODO: refactor this garbage cause it's only one char different from the previous one
|
||||
fn available_ipv6(&mut self, config: &Config) -> Option<VMNIC> {
|
||||
for nic in config.network_interfaces.iter() {
|
||||
for range in nic.ipv6.iter() {
|
||||
@ -198,6 +222,7 @@ impl Resources {
|
||||
}
|
||||
|
||||
fn reserve_vm_resources(&mut self, vm: &VM) {
|
||||
self.existing_vms.insert(vm.uuid.clone());
|
||||
self.reserved_vcpus += vm.vcpus;
|
||||
self.reserved_memory += vm.memory_mb;
|
||||
for nic in vm.nics.iter() {
|
||||
@ -212,15 +237,15 @@ impl Resources {
|
||||
self.reserved_ports.insert(*host_port);
|
||||
}
|
||||
|
||||
for storage_pool in self.storage_pools.iter_mut() {
|
||||
if storage_pool.path == vm.storage_pool_path {
|
||||
storage_pool.available_gb -= vm.disk_size_gb;
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.reserved_storage
|
||||
.entry(vm.storage_dir.clone())
|
||||
.and_modify(|gb| *gb += vm.disk_size_gb)
|
||||
.or_insert(vm.disk_size_gb);
|
||||
let _ = self.save_to_disk();
|
||||
}
|
||||
|
||||
fn free_vm_resources(&mut self, vm: &VM) {
|
||||
self.existing_vms.remove(&vm.uuid);
|
||||
self.reserved_vcpus -= vm.vcpus;
|
||||
self.reserved_memory -= vm.memory_mb;
|
||||
for nic in vm.nics.iter() {
|
||||
@ -234,10 +259,14 @@ impl Resources {
|
||||
for (host_port, _) in vm.fw_ports.iter() {
|
||||
self.reserved_ports.remove(host_port);
|
||||
}
|
||||
self.reserved_storage
|
||||
.entry(vm.storage_dir.clone())
|
||||
.and_modify(|gb| *gb -= vm.disk_size_gb);
|
||||
let _ = self.save_to_disk();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct StoragePool {
|
||||
path: String,
|
||||
available_gb: usize,
|
||||
@ -245,12 +274,11 @@ pub struct StoragePool {
|
||||
// tier: StorageTier,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum InterfaceConfig {
|
||||
// TODO: instead of QEMU userspace NAT, use iptables kernelspace NAT
|
||||
// in case of QEMU-base NAT, device name is not needed
|
||||
NAT { device: String },
|
||||
// TODO: figure how to calculate IF_NAME based on index
|
||||
MACVTAP { name: String, device: String },
|
||||
IPVTAP { name: String, device: String },
|
||||
Bridge { device: String },
|
||||
@ -291,7 +319,7 @@ impl InterfaceConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct IPConfig {
|
||||
address: String,
|
||||
// requires short format (example: 24)
|
||||
@ -299,7 +327,7 @@ struct IPConfig {
|
||||
gateway: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct VMNIC {
|
||||
if_config: InterfaceConfig,
|
||||
ips: Vec<IPConfig>,
|
||||
@ -311,9 +339,9 @@ impl VMNIC {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct VM {
|
||||
uuid: String,
|
||||
pub uuid: String,
|
||||
hostname: String,
|
||||
admin_key: String,
|
||||
fw_ports: Vec<(u16, u16)>,
|
||||
@ -325,7 +353,7 @@ pub struct VM {
|
||||
disk_size_gb: usize,
|
||||
kernel_sha: String,
|
||||
dtrfs_sha: String,
|
||||
storage_pool_path: String,
|
||||
storage_dir: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@ -353,8 +381,25 @@ impl NewVMRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct UpdateVMReq {
|
||||
pub uuid: String,
|
||||
vcpus: usize,
|
||||
memory_mb: usize,
|
||||
disk_size_gb: usize,
|
||||
}
|
||||
|
||||
impl UpdateVMReq {
|
||||
pub fn from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let request: UpdateVMReq = serde_yaml::from_str(&content)?;
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VMCreationErrors {
|
||||
VMAlreadyExists,
|
||||
NATandIPv4Conflict,
|
||||
TooManyCores,
|
||||
NotEnoughPorts,
|
||||
@ -363,7 +408,10 @@ pub enum VMCreationErrors {
|
||||
NotEnoughStorage,
|
||||
IPv4NotAvailable,
|
||||
IPv6NotAvailable,
|
||||
DiskTooSmall,
|
||||
ServerDiskError(String),
|
||||
BootFileError(String),
|
||||
HypervizorError(String),
|
||||
}
|
||||
|
||||
impl VM {
|
||||
@ -372,6 +420,9 @@ impl VM {
|
||||
config: &Config,
|
||||
res: &mut Resources,
|
||||
) -> Result<Self, VMCreationErrors> {
|
||||
if res.existing_vms.contains(&req.uuid) {
|
||||
return Err(VMCreationErrors::VMAlreadyExists);
|
||||
}
|
||||
if req.extra_ports.len() > 0 && req.public_ipv4 {
|
||||
return Err(VMCreationErrors::NATandIPv4Conflict);
|
||||
}
|
||||
@ -384,6 +435,9 @@ impl VM {
|
||||
if config.max_mem_reservation_mb < res.reserved_memory.saturating_add(req.memory_mb) {
|
||||
return Err(VMCreationErrors::NotEnoughMemory);
|
||||
}
|
||||
if req.disk_size_gb < 4 {
|
||||
return Err(VMCreationErrors::DiskTooSmall);
|
||||
}
|
||||
|
||||
if let Err(kernel_file_error) =
|
||||
res.download_boot_file(req.kernel_url, req.kernel_sha.clone())
|
||||
@ -440,7 +494,7 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
let storage_pool_path = match res.available_storage_pool(req.disk_size_gb) {
|
||||
let storage_pool_path = match res.available_storage_pool(req.disk_size_gb, config) {
|
||||
Some(path) => path,
|
||||
None => return Err(VMCreationErrors::NotEnoughStorage),
|
||||
};
|
||||
@ -456,40 +510,103 @@ impl VM {
|
||||
kernel_sha: req.kernel_sha,
|
||||
dtrfs_sha: req.dtrfs_sha,
|
||||
fw_ports: port_pairs,
|
||||
storage_pool_path,
|
||||
storage_dir: storage_pool_path,
|
||||
};
|
||||
|
||||
if let Err(e) = vm.write_config() {
|
||||
return Err(VMCreationErrors::ServerDiskError(e.to_string()));
|
||||
}
|
||||
res.reserve_vm_resources(&vm);
|
||||
Ok(vm)
|
||||
}
|
||||
|
||||
// TODO: test to see if this works
|
||||
pub fn update(
|
||||
&mut self,
|
||||
req: UpdateVMReq,
|
||||
config: &Config,
|
||||
res: &mut Resources,
|
||||
) -> Result<(), VMCreationErrors> {
|
||||
if config.max_cores_per_vm < req.vcpus {
|
||||
return Err(VMCreationErrors::TooManyCores);
|
||||
}
|
||||
if config.max_vcpu_reservation
|
||||
< res
|
||||
.reserved_vcpus
|
||||
.saturating_sub(self.vcpus)
|
||||
.saturating_add(req.vcpus)
|
||||
{
|
||||
return Err(VMCreationErrors::NotEnoughCPU);
|
||||
}
|
||||
if config.max_mem_reservation_mb
|
||||
< res
|
||||
.reserved_memory
|
||||
.saturating_sub(self.memory_mb)
|
||||
.saturating_add(req.memory_mb)
|
||||
{
|
||||
return Err(VMCreationErrors::NotEnoughMemory);
|
||||
}
|
||||
if req.disk_size_gb < self.disk_size_gb {
|
||||
return Err(VMCreationErrors::DiskTooSmall);
|
||||
}
|
||||
|
||||
res.reserved_memory -= self.memory_mb;
|
||||
res.reserved_memory += req.memory_mb;
|
||||
res.reserved_vcpus -= self.vcpus;
|
||||
res.reserved_vcpus += req.vcpus;
|
||||
|
||||
res.reserved_storage
|
||||
.entry(self.storage_dir.clone())
|
||||
.and_modify(|gb| {
|
||||
*gb -= self.disk_size_gb;
|
||||
*gb += req.disk_size_gb;
|
||||
});
|
||||
|
||||
self.memory_mb = req.memory_mb;
|
||||
self.vcpus = req.vcpus;
|
||||
self.disk_size_gb = req.disk_size_gb;
|
||||
|
||||
if let Err(e) = systemctl_stop_and_disable(&self.uuid) {
|
||||
return Err(VMCreationErrors::HypervizorError(e.to_string()));
|
||||
}
|
||||
|
||||
if let Err(e) = self.write_config() {
|
||||
return Err(VMCreationErrors::ServerDiskError(e.to_string()));
|
||||
}
|
||||
|
||||
if let Err(e) = self.write_sh_exports() {
|
||||
return Err(VMCreationErrors::ServerDiskError(e.to_string()));
|
||||
}
|
||||
|
||||
if let Err(e) = self.resize_disk() {
|
||||
return Err(VMCreationErrors::HypervizorError(e.to_string()));
|
||||
}
|
||||
|
||||
if let Err(e) = systemctl_stop_and_disable(&self.uuid) {
|
||||
return Err(VMCreationErrors::HypervizorError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<()> {
|
||||
self.create_disk()?;
|
||||
self.write_vm_config()?;
|
||||
self.write_sh_exports()?;
|
||||
self.write_systemd_unit_file()?;
|
||||
// TODO: systemctl daemon-reload
|
||||
// TODO: systemctl start
|
||||
// TODO: systemctl enable
|
||||
systemctl_reload()?;
|
||||
systemctl_start_and_enable(&self.uuid)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete(&self, res: &mut Resources) -> Result<()> {
|
||||
// TODO: systemctl stop
|
||||
// TODO: systemctl disable
|
||||
systemctl_stop_and_disable(&self.uuid)?;
|
||||
res.free_vm_resources(&self);
|
||||
self.delete_systemd_unit_file()?;
|
||||
// TODO: systemctl daemon-reload
|
||||
systemctl_reload()?;
|
||||
self.delete_disk()?;
|
||||
self.delete_vm_config()?;
|
||||
self.delete_sh_exports()?;
|
||||
self.delete_vtap_interfaces()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn deploy(&self) -> Result<()> {
|
||||
self.create_disk()?;
|
||||
self.write_vm_config()?;
|
||||
self.write_systemd_unit_file()?;
|
||||
// TODO: daemon-reload, systemctl enable, systemctl start
|
||||
self.delete_config()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -497,9 +614,9 @@ impl VM {
|
||||
// This means we can enforce the path to the disk.
|
||||
// This may change in the future as the VM is allowed to have multiple disks.
|
||||
pub fn disk_path(&self) -> String {
|
||||
let dir = match self.storage_pool_path.ends_with("/") {
|
||||
true => self.storage_pool_path.clone(),
|
||||
false => self.storage_pool_path.clone() + "/",
|
||||
let dir = match self.storage_dir.ends_with("/") {
|
||||
true => self.storage_dir.clone(),
|
||||
false => self.storage_dir.clone() + "/",
|
||||
};
|
||||
dir + &self.uuid + ".qcow2"
|
||||
}
|
||||
@ -507,6 +624,13 @@ impl VM {
|
||||
pub fn kernel_params(&self) -> String {
|
||||
let mut ip_string = String::new();
|
||||
let mut i = 0;
|
||||
if self.fw_ports.len() > 0 {
|
||||
ip_string += &format!(
|
||||
"detee_net_eth{}={}_{}_{} ",
|
||||
i, "10.0.2.15", "24", "10.0.2.2"
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
for nic in self.nics.iter() {
|
||||
for ip in nic.ips.iter() {
|
||||
ip_string += &format!(
|
||||
@ -521,13 +645,28 @@ impl VM {
|
||||
format!("{} {} {}", ip_string, admin_key, hostname)
|
||||
}
|
||||
|
||||
pub fn write_vm_config(&self) -> Result<()> {
|
||||
fn write_config(&self) -> Result<()> {
|
||||
let mut file = File::create(VM_CONFIG_DIR.to_string() + &self.uuid + ".yaml")?;
|
||||
file.write_all(serde_yaml::to_string(self)?.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_config(&self) -> Result<()> {
|
||||
remove_file(VM_CONFIG_DIR.to_string() + &self.uuid + ".yaml")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_sh_exports(&self) -> Result<()> {
|
||||
let mut vars = String::new();
|
||||
|
||||
let mut i = 0;
|
||||
for nic in self.nics.iter() {
|
||||
let mut interface = String::new();
|
||||
interface += &format!(r#"export NETWORK_INTERFACE_{}="{}"#, i, nic.if_config.if_type());
|
||||
interface += &format!(
|
||||
r#"export NETWORK_INTERFACE_{}="{}"#,
|
||||
i,
|
||||
nic.if_config.if_type()
|
||||
);
|
||||
// device is currently ignored in case of NAT cause we assume QEMU userspace NAT
|
||||
if let Some(vtap_name) = nic.if_config.vtap_name() {
|
||||
interface += &format!("_{}_{}", nic.if_config.device_name(), vtap_name);
|
||||
@ -543,8 +682,9 @@ impl VM {
|
||||
ports += &format!("{}:{} ", port.0, port.1);
|
||||
}
|
||||
if ports != "" {
|
||||
vars += &format!(r#"NAT_PORT_FW="{}""#, ports.trim_end());
|
||||
vars += &format!(r#"export NAT_PORT_FW="{}""#, ports.trim_end());
|
||||
vars += "\n";
|
||||
vars += "export NETWORK_INTERFACE_0000=NAT\n";
|
||||
}
|
||||
|
||||
vars += &format!(
|
||||
@ -570,17 +710,17 @@ impl VM {
|
||||
vars += &format!(r#"export DISK="{}""#, self.disk_path());
|
||||
vars += "\n";
|
||||
|
||||
let mut file = File::create(VM_CONFIG_DIR.to_string() + &self.uuid)?;
|
||||
let mut file = File::create(VM_CONFIG_DIR.to_string() + &self.uuid + ".sh")?;
|
||||
file.write_all(vars.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_vm_config(&self) -> Result<()> {
|
||||
remove_file(VM_CONFIG_DIR.to_string() + &self.uuid)?;
|
||||
fn delete_sh_exports(&self) -> Result<()> {
|
||||
remove_file(VM_CONFIG_DIR.to_string() + &self.uuid + ".sh")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_vtap_interfaces(&self) -> Result<()> {
|
||||
fn delete_vtap_interfaces(&self) -> Result<()> {
|
||||
for nic in self.nics.iter() {
|
||||
if let Some(name) = nic.if_config.vtap_name() {
|
||||
let result = Command::new("ip")
|
||||
@ -601,7 +741,7 @@ impl VM {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_systemd_unit_file(&self) -> Result<()> {
|
||||
fn write_systemd_unit_file(&self) -> Result<()> {
|
||||
let mut contents = String::new();
|
||||
contents += &format!("[Unit]\n");
|
||||
contents += &format!("Description=DeTEE {}\n", self.uuid);
|
||||
@ -617,17 +757,25 @@ impl VM {
|
||||
contents += &format!("[Install]\n");
|
||||
contents += &format!("WantedBy=multi-user.target\n");
|
||||
|
||||
let mut file = File::create("/etc/systemd/system/".to_string() + &self.uuid + ".service")?;
|
||||
let mut file =
|
||||
File::create_new("/etc/systemd/system/".to_string() + &self.uuid + ".service")?;
|
||||
file.write_all(contents.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_systemd_unit_file(&self) -> Result<()> {
|
||||
remove_file("/tmp/etc/systemd/system/".to_string() + &self.uuid + ".service")?;
|
||||
fn delete_systemd_unit_file(&self) -> Result<()> {
|
||||
remove_file("/etc/systemd/system/".to_string() + &self.uuid + ".service")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_disk(&self) -> Result<()> {
|
||||
fn create_disk(&self) -> Result<()> {
|
||||
if std::path::Path::new(&self.disk_path()).exists() {
|
||||
return Err(anyhow!(
|
||||
"Could not create {}. The file already exists.",
|
||||
self.disk_path()
|
||||
));
|
||||
}
|
||||
|
||||
let result = Command::new("qemu-img")
|
||||
.arg("create")
|
||||
.arg("-f")
|
||||
@ -647,12 +795,104 @@ impl VM {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_disk(&self) -> Result<()> {
|
||||
fn resize_disk(&self) -> Result<()> {
|
||||
let result = Command::new("qemu-img")
|
||||
.arg("resize")
|
||||
.arg(self.disk_path())
|
||||
.arg(self.disk_size_gb.to_string() + "G")
|
||||
.output()?;
|
||||
if !result.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Could not create VM Disk:\n{:?}\n{:?}",
|
||||
String::from_utf8(result.stdout)
|
||||
.unwrap_or("Could not grab stdout from creation script.".to_string()),
|
||||
String::from_utf8(result.stderr)
|
||||
.unwrap_or("Could not grab stderr from creation script.".to_string()),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_disk(&self) -> Result<()> {
|
||||
remove_file(self.disk_path())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn systemctl_start_and_enable(vm_uuid: &str) -> Result<()> {
|
||||
let result = Command::new("systemctl")
|
||||
.arg("start")
|
||||
.arg(vm_uuid.to_string() + ".service")
|
||||
.output()?;
|
||||
if !result.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Could not reload systemctl daemon:\n{:?}\n{:?}",
|
||||
String::from_utf8(result.stdout)
|
||||
.unwrap_or("Could not grab stdout from creation script.".to_string()),
|
||||
String::from_utf8(result.stderr)
|
||||
.unwrap_or("Could not grab stderr from creation script.".to_string()),
|
||||
));
|
||||
}
|
||||
let result = Command::new("systemctl")
|
||||
.arg("enable")
|
||||
.arg(vm_uuid.to_string() + ".service")
|
||||
.output()?;
|
||||
if !result.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Could not reload systemctl daemon:\n{:?}\n{:?}",
|
||||
String::from_utf8(result.stdout)
|
||||
.unwrap_or("Could not grab stdout from creation script.".to_string()),
|
||||
String::from_utf8(result.stderr)
|
||||
.unwrap_or("Could not grab stderr from creation script.".to_string()),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn systemctl_stop_and_disable(vm_uuid: &str) -> Result<()> {
|
||||
let result = Command::new("systemctl")
|
||||
.arg("stop")
|
||||
.arg(vm_uuid.to_string() + ".service")
|
||||
.output()?;
|
||||
if !result.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Could not reload systemctl daemon:\n{:?}\n{:?}",
|
||||
String::from_utf8(result.stdout)
|
||||
.unwrap_or("Could not grab stdout from creation script.".to_string()),
|
||||
String::from_utf8(result.stderr)
|
||||
.unwrap_or("Could not grab stderr from creation script.".to_string()),
|
||||
));
|
||||
}
|
||||
let result = Command::new("systemctl")
|
||||
.arg("disable")
|
||||
.arg(vm_uuid.to_string() + ".service")
|
||||
.output()?;
|
||||
if !result.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Could not reload systemctl daemon:\n{:?}\n{:?}",
|
||||
String::from_utf8(result.stdout)
|
||||
.unwrap_or("Could not grab stdout from creation script.".to_string()),
|
||||
String::from_utf8(result.stderr)
|
||||
.unwrap_or("Could not grab stderr from creation script.".to_string()),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn systemctl_reload() -> Result<()> {
|
||||
let result = Command::new("systemctl").arg("daemon-reload").output()?;
|
||||
if !result.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Could not reload systemctl daemon:\n{:?}\n{:?}",
|
||||
String::from_utf8(result.stdout)
|
||||
.unwrap_or("Could not grab stdout from creation script.".to_string()),
|
||||
String::from_utf8(result.stderr)
|
||||
.unwrap_or("Could not grab stderr from creation script.".to_string()),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn download_and_check_sha(url: &str, sha: &str) -> Result<()> {
|
||||
use reqwest::blocking::get;
|
||||
use std::fs::File;
|
||||
|
Loading…
Reference in New Issue
Block a user