added logic to reserve ips
This commit is contained in:
parent
edbaebfc56
commit
b905c96261
96
Cargo.lock
generated
96
Cargo.lock
generated
@ -8,6 +8,18 @@ version = "1.0.94"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cidr"
|
name = "cidr"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -23,9 +35,36 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cidr",
|
"cidr",
|
||||||
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.167"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.92"
|
version = "1.0.92"
|
||||||
@ -44,6 +83,36 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.215"
|
version = "1.0.215"
|
||||||
@ -80,3 +149,30 @@ name = "unicode-ident"
|
|||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.94"
|
anyhow = "1.0.94"
|
||||||
cidr = { version = "0.3.0", features = ["serde"] }
|
cidr = { version = "0.3.0", features = ["serde"] }
|
||||||
|
rand = "0.8.5"
|
||||||
serde = { version = "1.0.215", features = ["derive"] }
|
serde = { version = "1.0.215", features = ["derive"] }
|
||||||
|
@ -4,35 +4,47 @@ use cidr::Ipv6Cidr;
|
|||||||
use core::net::Ipv4Addr;
|
use core::net::Ipv4Addr;
|
||||||
use core::net::Ipv6Addr;
|
use core::net::Ipv6Addr;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
struct Volume {
|
pub struct Volume {
|
||||||
path: String,
|
path: String,
|
||||||
// maximum allowed storage in GB
|
// maximum allowed storage in GB
|
||||||
max_reservation: u64,
|
max_reservation: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Interface {
|
pub struct IPv4Range {
|
||||||
driver: InterfaceType,
|
pub subnet: Ipv4Cidr,
|
||||||
name: String,
|
pub gateway: Ipv4Addr,
|
||||||
ipv4_ranges: Vec<Ipv4Cidr>,
|
pub reserved_addrs: HashSet<Ipv4Addr>,
|
||||||
reserved_v4_addrs: Vec<Ipv4Addr>,
|
}
|
||||||
ipv6_ranges: Vec<Ipv6Cidr>,
|
|
||||||
reserved_v6_addrs: Vec<Ipv6Addr>,
|
pub struct IPv6Range {
|
||||||
|
pub subnet: Ipv6Cidr,
|
||||||
|
pub gateway: Ipv6Addr,
|
||||||
|
pub reserved_addrs: HashSet<Ipv6Addr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Interface {
|
||||||
|
pub driver: InterfaceType,
|
||||||
|
pub device: String,
|
||||||
|
pub ipv4: Vec<IPv4Range>,
|
||||||
|
pub ipv6: Vec<IPv6Range>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: create mechanic to autodetect interface type
|
// TODO: create mechanic to autodetect interface type
|
||||||
enum InterfaceType {
|
pub enum InterfaceType {
|
||||||
MACVTAP,
|
MACVTAP,
|
||||||
IPVTAP,
|
IPVTAP,
|
||||||
Bridge,
|
Bridge,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Config {
|
pub struct Config {
|
||||||
max_cores_per_vm: u64,
|
pub max_cores_per_vm: usize,
|
||||||
max_vcpu_reservation: u64,
|
pub max_vcpu_reservation: usize,
|
||||||
max_mem_reservation: u64,
|
pub max_mem_reservation: usize,
|
||||||
network_interfaces: Vec<Interface>,
|
pub network_interfaces: Vec<Interface>,
|
||||||
volumes: Vec<Volume>,
|
pub volumes: Vec<Volume>,
|
||||||
public_port_range: Range<u16>,
|
pub public_port_range: Range<u16>,
|
||||||
|
pub max_ports_per_vm: u16,
|
||||||
}
|
}
|
||||||
|
329
src/state.rs
329
src/state.rs
@ -1,18 +1,15 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
use crate::config::Config;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Range;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
type VMUUID = String;
|
pub enum InterfaceConfig {
|
||||||
|
|
||||||
pub enum NIC {
|
|
||||||
// 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 },
|
||||||
@ -22,89 +19,154 @@ pub enum NIC {
|
|||||||
Bridge { device: String },
|
Bridge { device: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum IPStatus {
|
|
||||||
Available,
|
|
||||||
Reserved(VMUUID),
|
|
||||||
Blacklisted,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IPData {
|
|
||||||
interface: NIC,
|
|
||||||
status: IPStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StoragePool {
|
pub struct StoragePool {
|
||||||
path: String,
|
path: String,
|
||||||
max_reservation: u64,
|
|
||||||
current_reservation: u64,
|
current_reservation: u64,
|
||||||
// add mechanic to detect storage tier
|
// add mechanic to detect storage tier
|
||||||
// tier: StorageTier,
|
// tier: StorageTier,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PortPool {
|
|
||||||
port_range: Range<u16>,
|
|
||||||
used_ports: HashSet<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Resources {
|
pub struct Resources {
|
||||||
// QEMU does not support MHz limiation
|
// QEMU does not support MHz limiation
|
||||||
mac_vcpus_reservation: u64,
|
reserved_vcpus: usize,
|
||||||
available_vcpus_reservation: u64,
|
reserved_memory: usize,
|
||||||
total_memory_reservation: u64,
|
used_ports: HashSet<u16>,
|
||||||
available_memory_reservation: u64,
|
|
||||||
// will be only one StoragePool for now and multiple later
|
|
||||||
storage_pools: Vec<StoragePool>,
|
storage_pools: Vec<StoragePool>,
|
||||||
ipv4_pool: HashMap<String, IPData>,
|
reserved_ipv4: HashSet<String>,
|
||||||
ipv6_pool: HashMap<String, IPData>,
|
reserved_ipv6: HashSet<String>,
|
||||||
|
reserved_if_names: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resources {
|
impl Resources {
|
||||||
fn get_available_ipv4(&self) -> usize {
|
fn reserve_vm_if(&mut self) -> String {
|
||||||
self.ipv4_pool
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
.values()
|
loop {
|
||||||
.filter(|ip_data| ip_data.status == IPStatus::Available)
|
let mut interface_name: String = rand::thread_rng()
|
||||||
.count()
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(9)
|
||||||
|
.map(char::from)
|
||||||
|
.collect();
|
||||||
|
interface_name = "detee".to_string() + &interface_name;
|
||||||
|
if !self.reserved_if_names.contains(&interface_name) {
|
||||||
|
return interface_name;
|
||||||
}
|
}
|
||||||
fn get_available_ipv6(&self) -> usize {
|
|
||||||
self.ipv6_pool
|
|
||||||
.values()
|
|
||||||
.filter(|ip_data| ip_data.status == IPStatus::Available)
|
|
||||||
.count()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NIC {
|
fn reserve_public_ipv4(&mut self, config: &Config) -> Option<VMNIC> {
|
||||||
|
for nic in config.network_interfaces.iter() {
|
||||||
|
for range in nic.ipv4.iter() {
|
||||||
|
for ip in range.subnet.iter() {
|
||||||
|
if !range.reserved_addrs.contains(&ip.address())
|
||||||
|
&& !self.reserved_ipv4.contains(&ip.to_string())
|
||||||
|
{
|
||||||
|
let if_config = match nic.driver {
|
||||||
|
crate::config::InterfaceType::MACVTAP => InterfaceConfig::MACVTAP {
|
||||||
|
name: self.reserve_vm_if(),
|
||||||
|
device: nic.device.clone(),
|
||||||
|
},
|
||||||
|
crate::config::InterfaceType::IPVTAP => InterfaceConfig::IPVTAP {
|
||||||
|
name: self.reserve_vm_if(),
|
||||||
|
device: nic.device.clone(),
|
||||||
|
},
|
||||||
|
crate::config::InterfaceType::Bridge => InterfaceConfig::Bridge {
|
||||||
|
device: nic.device.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut ips = Vec::new();
|
||||||
|
let mask = range
|
||||||
|
.subnet
|
||||||
|
.to_string()
|
||||||
|
.split('/')
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
ips.push(IPConfig {
|
||||||
|
address: ip.address().to_string(),
|
||||||
|
subnet: mask,
|
||||||
|
gateway: range.gateway.to_string(),
|
||||||
|
});
|
||||||
|
return Some(VMNIC { if_config, ips });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_public_ipv6(&mut self, config: &Config) -> Option<VMNIC> {
|
||||||
|
for nic in config.network_interfaces.iter() {
|
||||||
|
for range in nic.ipv6.iter() {
|
||||||
|
for ip in range.subnet.iter() {
|
||||||
|
if !range.reserved_addrs.contains(&ip.address())
|
||||||
|
&& !self.reserved_ipv6.contains(&ip.to_string())
|
||||||
|
{
|
||||||
|
let if_config = match nic.driver {
|
||||||
|
crate::config::InterfaceType::MACVTAP => InterfaceConfig::MACVTAP {
|
||||||
|
name: self.reserve_vm_if(),
|
||||||
|
device: nic.device.clone(),
|
||||||
|
},
|
||||||
|
crate::config::InterfaceType::IPVTAP => InterfaceConfig::IPVTAP {
|
||||||
|
name: self.reserve_vm_if(),
|
||||||
|
device: nic.device.clone(),
|
||||||
|
},
|
||||||
|
crate::config::InterfaceType::Bridge => InterfaceConfig::Bridge {
|
||||||
|
device: nic.device.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut ips = Vec::new();
|
||||||
|
let mask = range
|
||||||
|
.subnet
|
||||||
|
.to_string()
|
||||||
|
.split('/')
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
ips.push(IPConfig {
|
||||||
|
address: ip.address().to_string(),
|
||||||
|
subnet: mask,
|
||||||
|
gateway: range.gateway.to_string(),
|
||||||
|
});
|
||||||
|
return Some(VMNIC { if_config, ips });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceConfig {
|
||||||
fn if_type(&self) -> String {
|
fn if_type(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
NIC::IPVTAP { .. } => format!("ipvtap"),
|
InterfaceConfig::IPVTAP { .. } => format!("ipvtap"),
|
||||||
NIC::MACVTAP { .. } => format!("macvtap"),
|
InterfaceConfig::MACVTAP { .. } => format!("macvtap"),
|
||||||
NIC::NAT { .. } => format!("nat"),
|
InterfaceConfig::NAT { .. } => format!("nat"),
|
||||||
NIC::Bridge { .. } => format!("bridge"),
|
InterfaceConfig::Bridge { .. } => format!("bridge"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_name(&self) -> String {
|
fn device_name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
NIC::IPVTAP { device, .. } => device.clone(),
|
InterfaceConfig::IPVTAP { device, .. } => device.clone(),
|
||||||
NIC::MACVTAP { device, .. } => device.clone(),
|
InterfaceConfig::MACVTAP { device, .. } => device.clone(),
|
||||||
NIC::NAT { device, .. } => device.clone(),
|
InterfaceConfig::NAT { device, .. } => device.clone(),
|
||||||
NIC::Bridge { device, .. } => device.clone(),
|
InterfaceConfig::Bridge { device, .. } => device.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vtap_name(&self) -> Option<String> {
|
fn vtap_name(&self) -> Option<String> {
|
||||||
match self {
|
match self {
|
||||||
NIC::IPVTAP { name, .. } => Some(name.clone()),
|
InterfaceConfig::IPVTAP { name, .. } => Some(name.clone()),
|
||||||
NIC::MACVTAP { name, .. } => Some(name.clone()),
|
InterfaceConfig::MACVTAP { name, .. } => Some(name.clone()),
|
||||||
NIC::NAT { .. } | NIC::Bridge { .. } => None,
|
InterfaceConfig::NAT { .. } | InterfaceConfig::Bridge { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_vtap(&self) -> bool {
|
fn is_vtap(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
NIC::IPVTAP { .. } | NIC::MACVTAP { .. } => true,
|
InterfaceConfig::IPVTAP { .. } | InterfaceConfig::MACVTAP { .. } => true,
|
||||||
NIC::NAT { .. } | NIC::Bridge { .. } => false,
|
InterfaceConfig::NAT { .. } | InterfaceConfig::Bridge { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,42 +176,152 @@ struct IPConfig {
|
|||||||
// requires short format (example: 24)
|
// requires short format (example: 24)
|
||||||
subnet: String,
|
subnet: String,
|
||||||
gateway: String,
|
gateway: String,
|
||||||
nameserver: String,
|
}
|
||||||
nic: NIC,
|
|
||||||
|
pub struct VMNIC {
|
||||||
|
if_config: InterfaceConfig,
|
||||||
|
ips: Vec<IPConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VM {
|
pub struct VM {
|
||||||
uuid: VMUUID,
|
uuid: String,
|
||||||
hostname: String,
|
hostname: String,
|
||||||
admin_key: String,
|
admin_key: String,
|
||||||
ips: Vec<IPConfig>,
|
fw_ports: Vec<u16>,
|
||||||
|
nics: Vec<VMNIC>,
|
||||||
// currently hardcoded to EPYC-v4
|
// currently hardcoded to EPYC-v4
|
||||||
// cpu_type: String,
|
// cpu_type: String,
|
||||||
vcpus: u32,
|
vcpus: usize,
|
||||||
// memory in MB
|
// memory in MB
|
||||||
memory: u32,
|
memory: usize,
|
||||||
// disk size in GB
|
// disk size in GB
|
||||||
disk_size: u32,
|
disk_size: usize,
|
||||||
kernel_path: String,
|
kernel_path: String,
|
||||||
|
kernel_sha: String,
|
||||||
initrd_path: String,
|
initrd_path: String,
|
||||||
ovmf_path: String,
|
initrd_sha: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NewVMRequest {
|
||||||
|
uuid: String,
|
||||||
|
hostname: String,
|
||||||
|
admin_key: String,
|
||||||
|
forwarded_ports: Vec<u16>,
|
||||||
|
public_ipv4: bool,
|
||||||
|
public_ipv6: bool,
|
||||||
|
disk_size: usize,
|
||||||
|
vcpus: usize,
|
||||||
|
memory: usize,
|
||||||
|
kernel_path: String,
|
||||||
|
kernel_sha: String,
|
||||||
|
initrd_path: String,
|
||||||
|
initrd_sha: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum VMCreationErrors {
|
||||||
|
NATandIPv4Conflict,
|
||||||
|
TooManyCores,
|
||||||
|
NotEnoughCPU,
|
||||||
|
NotEnoughMemory,
|
||||||
|
NotEnoughStorage,
|
||||||
|
IPv4NotAvailable,
|
||||||
|
IPv6NotAvailable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VM {
|
impl VM {
|
||||||
|
pub fn new(
|
||||||
|
req: NewVMRequest,
|
||||||
|
config: &Config,
|
||||||
|
res: &mut Resources,
|
||||||
|
) -> Result<Self, VMCreationErrors> {
|
||||||
|
if req.forwarded_ports.len() > 0 && req.public_ipv4 {
|
||||||
|
return Err(VMCreationErrors::NATandIPv4Conflict);
|
||||||
|
}
|
||||||
|
if config.max_cores_per_vm < req.vcpus {
|
||||||
|
return Err(VMCreationErrors::TooManyCores);
|
||||||
|
}
|
||||||
|
if config.max_vcpu_reservation < res.reserved_vcpus.saturating_add(req.vcpus) {
|
||||||
|
return Err(VMCreationErrors::NotEnoughCPU);
|
||||||
|
}
|
||||||
|
if config.max_mem_reservation < res.reserved_memory.saturating_add(req.memory) {
|
||||||
|
return Err(VMCreationErrors::NotEnoughMemory);
|
||||||
|
}
|
||||||
|
let mut vm_nics = Vec::new();
|
||||||
|
if req.public_ipv4 {
|
||||||
|
match res.reserve_public_ipv4(config) {
|
||||||
|
Some(vmnic) => vm_nics.push(vmnic),
|
||||||
|
None => return Err(VMCreationErrors::IPv4NotAvailable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.public_ipv6 {
|
||||||
|
match res.reserve_public_ipv4(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() {
|
||||||
|
vmnic.ips.append(&mut existing_vmnic.ips);
|
||||||
|
vm_nics.push(vmnic);
|
||||||
|
} else {
|
||||||
|
vm_nics.push(existing_vmnic);
|
||||||
|
vm_nics.push(vmnic);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vm_nics.push(vmnic);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => return Err(VMCreationErrors::IPv4NotAvailable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(VM {
|
||||||
|
uuid: req.uuid,
|
||||||
|
hostname: req.hostname,
|
||||||
|
admin_key: req.admin_key,
|
||||||
|
nics: vm_nics,
|
||||||
|
vcpus: req.vcpus,
|
||||||
|
memory: req.memory,
|
||||||
|
disk_size: req.disk_size,
|
||||||
|
kernel_sha: req.kernel_sha,
|
||||||
|
initrd_sha: req.initrd_sha,
|
||||||
|
fw_ports: todo!(),
|
||||||
|
kernel_path: todo!(),
|
||||||
|
initrd_path: todo!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(&self) -> Result<()> {
|
||||||
|
// TODO: systemctl disable, systemctl stop
|
||||||
|
self.delete_disk()?;
|
||||||
|
self.delete_vtap_interfaces()?;
|
||||||
|
self.delete_systemd_unit_file()?;
|
||||||
|
// TODO: systemctl daemon-reload
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// For the MVP, the T-Contract offers VM+IP+Disk as a bundle.
|
// For the MVP, the T-Contract offers VM+IP+Disk as a bundle.
|
||||||
// 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 {
|
||||||
VM_DISK_DIR.to_string() + "/" + &self.uuid + ".qcow2"
|
VM_DISK_DIR.to_string() + "/" + &self.uuid + ".qcow2"
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
for ip in self.ips.iter() {
|
for nic in self.nics.iter() {
|
||||||
|
for ip in nic.ips.iter() {
|
||||||
ip_string += &format!(
|
ip_string += &format!(
|
||||||
"detee_net_eth{}={}_{}_{}_{}",
|
"detee_net_eth{}={}_{}_{}",
|
||||||
i, ip.address, ip.subnet, ip.gateway, ip.nameserver
|
i, ip.address, ip.subnet, ip.gateway
|
||||||
);
|
);
|
||||||
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
let admin_key = format!("detee_admin={}", self.admin_key);
|
let admin_key = format!("detee_admin={}", self.admin_key);
|
||||||
@ -157,16 +329,16 @@ impl VM {
|
|||||||
format!("{} {} {}", ip_string, admin_key, hostname)
|
format!("{} {} {}", ip_string, admin_key, hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export_vm_env(&self) -> String {
|
pub fn write_vm_config(&self) -> Result<()> {
|
||||||
let mut vars = String::new();
|
let mut vars = String::new();
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
for ip in self.ips.iter() {
|
for nic in self.nics.iter() {
|
||||||
let mut interface = String::new();
|
let mut interface = String::new();
|
||||||
interface += &format!("NETWORK_INTERFACE_{}={}", i, ip.nic.if_type());
|
interface += &format!("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) = ip.nic.vtap_name() {
|
if let Some(vtap_name) = nic.if_config.vtap_name() {
|
||||||
interface += &format!("_{}_{}", ip.nic.device_name(), vtap_name);
|
interface += &format!("_{}_{}", nic.if_config.device_name(), vtap_name);
|
||||||
}
|
}
|
||||||
vars += &format!("{}\n", interface);
|
vars += &format!("{}\n", interface);
|
||||||
i += 1;
|
i += 1;
|
||||||
@ -182,12 +354,19 @@ impl VM {
|
|||||||
vars += &format!("DISK={}\n", self.disk_path());
|
vars += &format!("DISK={}\n", self.disk_path());
|
||||||
vars += &format!("DISK_SIZE={}GB\n", self.disk_size);
|
vars += &format!("DISK_SIZE={}GB\n", self.disk_size);
|
||||||
|
|
||||||
todo!();
|
let mut file = File::create(VM_CONFIG_DIR.to_string() + "/" + &self.uuid)?;
|
||||||
|
file.write_all(vars.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_vm_config(&self) -> Result<()> {
|
||||||
|
remove_file(VM_CONFIG_DIR.to_string() + "/" + &self.uuid)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_vtap_interfaces(&self) -> Result<()> {
|
pub fn delete_vtap_interfaces(&self) -> Result<()> {
|
||||||
for ip in self.ips.iter() {
|
for nic in self.nics.iter() {
|
||||||
if let Some(name) = ip.nic.vtap_name() {
|
if let Some(name) = nic.if_config.vtap_name() {
|
||||||
let result = Command::new("ip")
|
let result = Command::new("ip")
|
||||||
.arg("link")
|
.arg("link")
|
||||||
.arg("del")
|
.arg("del")
|
||||||
@ -222,13 +401,13 @@ impl VM {
|
|||||||
contents += &format!("[Install]");
|
contents += &format!("[Install]");
|
||||||
contents += &format!("WantedBy=multi-user.target");
|
contents += &format!("WantedBy=multi-user.target");
|
||||||
|
|
||||||
let mut file = File::create(VM_CONFIG_DIR.to_string() + "/" + &self.uuid)?;
|
let mut file = File::create("/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<()> {
|
pub fn delete_systemd_unit_file(&self) -> Result<()> {
|
||||||
remove_file(VM_CONFIG_DIR.to_string() + "/" + &self.uuid)?;
|
remove_file("/etc/systemd/system/".to_string() + &self.uuid + ".service")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,10 @@ pub struct FinalizedTContract {
|
|||||||
pub owner: String,
|
pub owner: String,
|
||||||
pub user: String,
|
pub user: String,
|
||||||
pub alloc: ResourceAllocation,
|
pub alloc: ResourceAllocation,
|
||||||
|
pub kernel_uri: String,
|
||||||
|
pub kernel_sha: String,
|
||||||
|
pub initrd_uri: String,
|
||||||
|
pub initrd_sha: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
Loading…
Reference in New Issue
Block a user