From d4c5cc263467413a7b12ec75a8ecf8ab0adca686 Mon Sep 17 00:00:00 2001 From: ghe0 Date: Thu, 12 Dec 2024 02:08:53 +0200 Subject: [PATCH] correctly assigning and storing IPs and interfaces --- src/constants.rs | 2 +- src/main.rs | 26 +++++++--- src/state.rs | 100 ++++++++++++++++++++++++------------- test_data/config2.yaml | 15 +++--- test_data/new_vm_req1.yaml | 12 ++--- test_data/new_vm_req2.yaml | 9 ++-- test_data/new_vm_req3.yaml | 17 +++---- test_data/new_vm_req4.yaml | 9 ++-- 8 files changed, 110 insertions(+), 80 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 6fced57..6728c7f 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,7 +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 VM_DISK_DIR: &str = "/var/lib/detee/boot/"; +pub(crate) const VM_DISK_DIR: &str = "/var/lib/detee/vms/"; pub(crate) const VM_CONFIG_DIR: &str = "/etc/detee/daemon/vms/"; pub(crate) const DAEMON_CONFIG_PATH: &str = "/etc/detee/daemon/config.json"; pub(crate) const START_VM_SCRIPT: &str = "/usr/local/bin/detee/start_qemu_vm.sh"; diff --git a/src/main.rs b/src/main.rs index 19d9eec..c5306b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,10 @@ use crate::config::Config; use crate::state::NewVMRequest; fn main() -> Result<(), Box> { - // let config = Config::from_file("test_data/config1.yaml")?; + let mut res = state::Resources::new(); + let config = Config::from_file("test_data/config2.yaml")?; // println!("{:#?}", config); + // let config = Config::from_file("test_data/config2.yaml")?; // println!("{:#?}", config); // let config = Config::from_file("test_data/config3.yaml")?; @@ -17,13 +19,21 @@ fn main() -> Result<(), Box> { // println!("{:#?}", config); // let config = Config::from_file("test_data/config5.yaml")?; // println!("{:#?}", config); - let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req1.yaml")?; - println!("{:#?}", new_vm_req); - let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req2.yaml")?; - println!("{:#?}", new_vm_req); - let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req3.yaml")?; - println!("{:#?}", new_vm_req); let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req4.yaml")?; - println!("{:#?}", new_vm_req); + // println!("{:#?}", new_vm_req); + + // let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req2.yaml")?; + // println!("{:#?}", new_vm_req); + // let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req3.yaml")?; + // println!("{:#?}", new_vm_req); + // let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req4.yaml")?; + // println!("{:#?}", new_vm_req); + let vm = state::VM::new(new_vm_req, &config, &mut res); + let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req4.yaml")?; + let vm = state::VM::new(new_vm_req, &config, &mut res); + let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req4.yaml")?; + let vm = state::VM::new(new_vm_req, &config, &mut res); + println!("vm: {:#?}", vm); + println!("res: {:#?}", res); Ok(()) } diff --git a/src/state.rs b/src/state.rs index ba87053..20d0625 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,6 +3,7 @@ use crate::config::Config; use crate::constants::*; use anyhow::anyhow; use anyhow::Result; +use serde::Deserialize; use sha2::{Digest, Sha256}; use std::collections::HashSet; use std::fs; @@ -12,8 +13,8 @@ use std::io::Read; use std::io::Write; use std::path::Path; use std::process::Command; -use serde::Deserialize; +#[derive(Debug)] pub struct Resources { // QEMU does not support MHz limiation reserved_vcpus: usize, @@ -27,7 +28,18 @@ pub struct Resources { } impl Resources { - fn reserve_ports(&mut self, extra_ports: usize, config: &Config) -> Vec { + pub fn new() -> Self { + Resources { + reserved_vcpus: 0, + reserved_memory: 0, + reserved_ports: HashSet::new(), + storage_pools: Vec::new(), + reserved_ips: HashSet::new(), + reserved_if_names: HashSet::new(), + boot_files: HashSet::new(), + } + } + fn get_free_ports(&mut self, extra_ports: usize, config: &Config) -> Vec { use rand::Rng; let total_ports = extra_ports + 1; if config.public_port_range.len() < self.reserved_ports.len() + total_ports as usize { @@ -40,7 +52,7 @@ impl Resources { for _ in 0..total_ports { for _ in 0..5 { let port = rand::thread_rng().gen_range(config.public_port_range.clone()); - if self.reserved_ports.insert(port) { + if self.reserved_ports.get(&port).is_none() { published_ports.push(port); } break; @@ -50,7 +62,7 @@ impl Resources { published_ports } - fn reserve_vm_if(&mut self) -> String { + fn get_free_vm_name(&mut self) -> String { use rand::{distributions::Alphanumeric, Rng}; loop { let mut interface_name: String = rand::thread_rng() @@ -65,20 +77,21 @@ impl Resources { } } - fn reserve_public_ipv4(&mut self, config: &Config) -> Option { + fn get_free_ipv4(&mut self, config: &Config) -> Option { for nic in config.network_interfaces.iter() { for range in nic.ipv4.iter() { - for ip in range.subnet.iter() { + for ip in range.subnet.iter().skip(1) { if !range.reserved_addrs.contains(&ip.address()) - && !self.reserved_ips.contains(&ip.to_string()) + && !self.reserved_ips.contains(&ip.address().to_string()) + && ip.address() != range.gateway { let if_config = match nic.driver { crate::config::InterfaceType::MACVTAP => InterfaceConfig::MACVTAP { - name: self.reserve_vm_if(), + name: self.get_free_vm_name(), device: nic.device.clone(), }, crate::config::InterfaceType::IPVTAP => InterfaceConfig::IPVTAP { - name: self.reserve_vm_if(), + name: self.get_free_vm_name(), device: nic.device.clone(), }, crate::config::InterfaceType::Bridge => InterfaceConfig::Bridge { @@ -90,12 +103,12 @@ impl Resources { .network() .to_string() .split('/') - .next() + .nth(1) .unwrap() .to_string(); ips.push(IPConfig { address: ip.address().to_string(), - subnet: mask, + mask, gateway: range.gateway.to_string(), }); return Some(VMNIC { if_config, ips }); @@ -107,20 +120,21 @@ impl Resources { } // TODO: refactor this garbage cause it's only one char different from the previous one - fn reserve_public_ipv6(&mut self, config: &Config) -> Option { + fn get_free_ipv6(&mut self, config: &Config) -> Option { for nic in config.network_interfaces.iter() { for range in nic.ipv6.iter() { - for ip in range.subnet.iter() { + for ip in range.subnet.iter().skip(1) { if !range.reserved_addrs.contains(&ip.address()) - && !self.reserved_ips.contains(&ip.to_string()) + && !self.reserved_ips.contains(&ip.address().to_string()) + && ip.address() != range.gateway { let if_config = match nic.driver { crate::config::InterfaceType::MACVTAP => InterfaceConfig::MACVTAP { - name: self.reserve_vm_if(), + name: self.get_free_vm_name(), device: nic.device.clone(), }, crate::config::InterfaceType::IPVTAP => InterfaceConfig::IPVTAP { - name: self.reserve_vm_if(), + name: self.get_free_vm_name(), device: nic.device.clone(), }, crate::config::InterfaceType::Bridge => InterfaceConfig::Bridge { @@ -132,12 +146,12 @@ impl Resources { .network() .to_string() .split('/') - .next() + .nth(1) .unwrap() .to_string(); ips.push(IPConfig { address: ip.address().to_string(), - subnet: mask, + mask, gateway: range.gateway.to_string(), }); return Some(VMNIC { if_config, ips }); @@ -175,8 +189,25 @@ impl Resources { self.boot_files.insert(sha); Ok(()) } + + fn reserve_vm_resources(&mut self, vm: &VM) { + self.reserved_vcpus += vm.vcpus; + self.reserved_memory += vm.memory; + for nic in vm.nics.iter() { + if let Some(vtap) = nic.if_config.vtap_name() { + self.reserved_if_names.insert(vtap); + } + for ip in nic.ips.iter() { + self.reserved_ips.insert(ip.address.clone()); + } + } + for (host_port, _) in vm.fw_ports.iter() { + self.reserved_ports.insert(*host_port); + } + } } +#[derive(Debug)] pub struct StoragePool { path: String, current_reservation: u64, @@ -184,6 +215,7 @@ pub struct StoragePool { // tier: StorageTier, } +#[derive(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 @@ -229,13 +261,15 @@ impl InterfaceConfig { } } +#[derive(Debug)] struct IPConfig { address: String, // requires short format (example: 24) - subnet: String, + mask: String, gateway: String, } +#[derive(Debug)] pub struct VMNIC { if_config: InterfaceConfig, ips: Vec, @@ -247,6 +281,7 @@ impl VMNIC { } } +#[derive(Debug)] pub struct VM { uuid: String, hostname: String, @@ -289,6 +324,7 @@ impl NewVMRequest { } } +#[derive(Debug)] pub enum VMCreationErrors { NATandIPv4Conflict, TooManyCores, @@ -336,13 +372,13 @@ impl VM { let mut vm_nics = Vec::new(); if req.public_ipv4 { - match res.reserve_public_ipv4(config) { + match res.get_free_ipv4(config) { Some(vmnic) => vm_nics.push(vmnic), None => return Err(VMCreationErrors::IPv4NotAvailable), } } if req.public_ipv6 { - match res.reserve_public_ipv4(config) { + match res.get_free_ipv6(config) { Some(mut vmnic) => { if let Some(mut existing_vmnic) = vm_nics.pop() { if vmnic.if_config.device_name() == existing_vmnic.if_config.device_name() { @@ -357,11 +393,6 @@ impl VM { } } None => { - if let Some(existing_vmnic) = vm_nics.pop() { - for ip in existing_vmnic.ips { - res.reserved_ips.remove(&ip.address); - } - } return Err(VMCreationErrors::IPv4NotAvailable); } } @@ -370,22 +401,17 @@ impl VM { let mut host_ports: Vec = Vec::new(); let mut port_pairs: Vec<(u16, u16)> = Vec::new(); if !req.public_ipv4 { - host_ports.append(res.reserve_ports(req.extra_ports.len(), &config).as_mut()); + host_ports.append(res.get_free_ports(req.extra_ports.len(), &config).as_mut()); if host_ports.len() == 0 { - for nic in vm_nics { - for ip in nic.ips { - res.reserved_ips.remove(&ip.address); - } - } return Err(VMCreationErrors::NotEnoughPorts); } port_pairs.push((host_ports[0], 22)); for i in 0..req.extra_ports.len() { - port_pairs.push((host_ports[i+1], req.extra_ports[i])); + port_pairs.push((host_ports[i + 1], req.extra_ports[i])); } } - Ok(VM { + let vm = VM { uuid: req.uuid, hostname: req.hostname, admin_key: req.admin_key, @@ -396,7 +422,9 @@ impl VM { kernel_sha: req.kernel_sha, dtrfs_sha: req.dtrfs_sha, fw_ports: port_pairs, - }) + }; + res.reserve_vm_resources(&vm); + Ok(vm) } pub fn deploy(&self) -> Result<()> { @@ -430,7 +458,7 @@ impl VM { for ip in nic.ips.iter() { ip_string += &format!( "detee_net_eth{}={}_{}_{}", - i, ip.address, ip.subnet, ip.gateway + i, ip.address, ip.mask, ip.gateway ); } i += 1; diff --git a/test_data/config2.yaml b/test_data/config2.yaml index 9c36395..9e79f2e 100644 --- a/test_data/config2.yaml +++ b/test_data/config2.yaml @@ -1,6 +1,6 @@ max_cores_per_vm: 16 max_vcpu_reservation: 32 -max_mem_reservation: 65536 +max_mem_reservation: 1265536 network_interfaces: - driver: "Bridge" device: "br0" @@ -11,11 +11,7 @@ network_interfaces: - "10.0.0.100" - "10.0.0.101" - "10.0.0.102" - ipv6: - - subnet: "fd00::/48" - gateway: "fd00::1" - reserved_addrs: - - "fd00::1000" + ipv6: [] - driver: "IPVTAP" device: "tap1" ipv4: @@ -24,7 +20,12 @@ network_interfaces: reserved_addrs: - "172.16.0.10" - "172.16.0.11" - ipv6: [] + ipv6: + - subnet: "2001:db8:abcd:1234::/64" + gateway: "2001:db8:abcd:1234::1" + reserved_addrs: + - "2001:db8:abcd:1234::dead" + - "2001:db8:abcd:1234::beef" volumes: - path: "/data/volume1" max_reservation: 500 diff --git a/test_data/new_vm_req1.yaml b/test_data/new_vm_req1.yaml index 0494514..f6225da 100644 --- a/test_data/new_vm_req1.yaml +++ b/test_data/new_vm_req1.yaml @@ -1,15 +1,13 @@ uuid: "123e4567-e89b-12d3-a456-426614174000" hostname: "test-vm-01" admin_key: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkeyexample" -extra_ports: - - 8080 - - 8443 +extra_ports: [ ] public_ipv4: true public_ipv6: false disk_size: 50 vcpus: 4 memory: 8192 -kernel_url: "http://example.com/kernel" -kernel_sha: "abc123def4567890ghij" -dtrfs_url: "http://example.com/dtrfs" -dtrfs_sha: "xyz9876543210mnop" +kernel_url: "http://pb1n.de/?d25eec" +kernel_sha: "be29dfef7157bfe860e94e96dcfab2318c5006e92e8d846a3ad7aa804b3b994e" +dtrfs_url: "http://pb1n.de/?e46db9" +dtrfs_sha: "62e7362c9350d60698cae6eed302562a2b41bec1d248889baad302da19c3bb47" diff --git a/test_data/new_vm_req2.yaml b/test_data/new_vm_req2.yaml index 6d34574..f087123 100644 --- a/test_data/new_vm_req2.yaml +++ b/test_data/new_vm_req2.yaml @@ -7,8 +7,7 @@ public_ipv6: false disk_size: 10 vcpus: 1 memory: 2048 -kernel_url: "http://minimal.com/kernel" -kernel_sha: "minimalsha123" -dtrfs_url: "http://minimal.com/dtrfs" -dtrfs_sha: "dtrfssha456" - +kernel_url: "http://pb1n.de/?d25eec" +kernel_sha: "be29dfef7157bfe860e94e96dcfab2318c5006e92e8d846a3ad7aa804b3b994e" +dtrfs_url: "http://pb1n.de/?e46db9" +dtrfs_sha: "62e7362c9350d60698cae6eed302562a2b41bec1d248889baad302da19c3bb47" diff --git a/test_data/new_vm_req3.yaml b/test_data/new_vm_req3.yaml index 706cc9d..e016f92 100644 --- a/test_data/new_vm_req3.yaml +++ b/test_data/new_vm_req3.yaml @@ -1,19 +1,14 @@ uuid: "246e1357-e98b-76d3-f345-129874650000" hostname: "extensive-vm" admin_key: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEExtendedKeyExample" -extra_ports: - - 80 - - 443 - - 3000 - - 5000 - - 6000 +extra_ports: [] public_ipv4: true public_ipv6: true disk_size: 200 -vcpus: 16 +vcpus: 2 memory: 65536 -kernel_url: "http://large.com/kernel" -kernel_sha: "largekernelsha" -dtrfs_url: "http://large.com/dtrfs" -dtrfs_sha: "largedtrfssha" +kernel_url: "http://pb1n.de/?d25eec" +kernel_sha: "be29dfef7157bfe860e94e96dcfab2318c5006e92e8d846a3ad7aa804b3b994e" +dtrfs_url: "http://pb1n.de/?e46db9" +dtrfs_sha: "62e7362c9350d60698cae6eed302562a2b41bec1d248889baad302da19c3bb47" diff --git a/test_data/new_vm_req4.yaml b/test_data/new_vm_req4.yaml index 0ea6697..91f5217 100644 --- a/test_data/new_vm_req4.yaml +++ b/test_data/new_vm_req4.yaml @@ -7,8 +7,7 @@ public_ipv6: true disk_size: 25 vcpus: 2 memory: 4096 -kernel_url: "http://testing.com/kernel" -kernel_sha: "testkernelsha" -dtrfs_url: "http://testing.com/dtrfs" -dtrfs_sha: "testdtrfssha" - +kernel_url: "http://pb1n.de/?d25eec" +kernel_sha: "be29dfef7157bfe860e94e96dcfab2318c5006e92e8d846a3ad7aa804b3b994e" +dtrfs_url: "http://pb1n.de/?e46db9" +dtrfs_sha: "62e7362c9350d60698cae6eed302562a2b41bec1d248889baad302da19c3bb47"