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