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:
ghe0 2024-12-14 01:10:30 +02:00
parent 82273f63e5
commit b85feef0ab
Signed by: ghe0
GPG Key ID: 451028EE56A0FBB4
9 changed files with 404 additions and 82 deletions

@ -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"

@ -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"

@ -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";

@ -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(())
}

@ -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;