feat: assign mac / gateway
This commit is contained in:
parent
1cc4d40082
commit
06d8339653
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
**/target
|
||||
.idea/
|
||||
.idea/
|
||||
*.config.yaml
|
108
Cargo.lock
generated
108
Cargo.lock
generated
@ -69,6 +69,55 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.91"
|
||||
@ -474,6 +523,52 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@ -1695,6 +1790,12 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
@ -1781,6 +1882,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"clap",
|
||||
"config",
|
||||
"fedimint-tonic-lnd",
|
||||
"ipnetwork",
|
||||
@ -4098,6 +4200,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
@ -25,3 +25,4 @@ urlencoding = "2.1.3"
|
||||
fedimint-tonic-lnd = { version = "0.2.0", default-features = false, features = ["invoicesrpc"] }
|
||||
ipnetwork = "0.20.0"
|
||||
rand = "0.8.5"
|
||||
clap = { version = "4.5.21", features = ["derive"] }
|
||||
|
@ -1,4 +1,3 @@
|
||||
# MySQL database connection string
|
||||
db: "mysql://root:root@localhost:3376/lnvps"
|
||||
lnd:
|
||||
url: "https://127.0.0.1:10003"
|
||||
|
4
lnvps_db/migrations/20241127163556_networking.sql
Normal file
4
lnvps_db/migrations/20241127163556_networking.sql
Normal file
@ -0,0 +1,4 @@
|
||||
alter table ip_range
|
||||
add column gateway varchar(255) not null;
|
||||
alter table vm
|
||||
add column mac_address varchar(20) not null;
|
@ -1,4 +1,4 @@
|
||||
use crate::{LNVpsDb, Vm, VmTemplate};
|
||||
use crate::{LNVpsDb, Vm, VmIpAssignment, VmTemplate};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use std::ops::Deref;
|
||||
@ -49,3 +49,15 @@ impl<D: Deref<Target = dyn LNVpsDb> + Sync> Hydrate<D> for VmTemplate {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<D: Deref<Target = dyn LNVpsDb> + Sync> Hydrate<D> for VmIpAssignment {
|
||||
async fn hydrate_up(&mut self, db: &D) -> Result<()> {
|
||||
self.ip_range = Some(db.get_ip_range(self.ip_range_id).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn hydrate_down(&mut self, db: &D) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,9 @@ pub trait LNVpsDb: Sync + Send {
|
||||
/// List available OS images
|
||||
async fn list_os_image(&self) -> Result<Vec<VmOsImage>>;
|
||||
|
||||
/// List available IP Ranges
|
||||
async fn get_ip_range(&self, id: u64) -> Result<IpRange>;
|
||||
|
||||
/// List available IP Ranges
|
||||
async fn list_ip_range(&self) -> Result<Vec<IpRange>>;
|
||||
|
||||
|
@ -149,6 +149,7 @@ impl VmOsImage {
|
||||
pub struct IpRange {
|
||||
pub id: u64,
|
||||
pub cidr: String,
|
||||
pub gateway: String,
|
||||
pub enabled: bool,
|
||||
pub region_id: u64,
|
||||
}
|
||||
@ -225,6 +226,8 @@ pub struct Vm {
|
||||
pub disk_size: u64,
|
||||
/// The [VmHostDisk] this VM is on
|
||||
pub disk_id: u64,
|
||||
/// Network MAC address
|
||||
pub mac_address: String,
|
||||
|
||||
#[sqlx(skip)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@ -244,12 +247,16 @@ pub struct Vm {
|
||||
pub ip_assignments: Option<Vec<VmIpAssignment>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, FromRow, Clone, Debug, Default)]
|
||||
pub struct VmIpAssignment {
|
||||
pub id: u64,
|
||||
pub vm_id: u64,
|
||||
pub ip_range_id: u64,
|
||||
pub ip: String,
|
||||
|
||||
#[sqlx(skip)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ip_range: Option<IpRange>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
|
@ -152,6 +152,14 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn get_ip_range(&self, id: u64) -> Result<IpRange> {
|
||||
sqlx::query_as("select * from ip_range where id=?")
|
||||
.bind(id)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn list_ip_range(&self) -> Result<Vec<IpRange>> {
|
||||
sqlx::query_as("select * from ip_range")
|
||||
.fetch_all(&self.db)
|
||||
@ -206,7 +214,7 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
}
|
||||
|
||||
async fn insert_vm(&self, vm: &Vm) -> Result<u64> {
|
||||
Ok(sqlx::query("insert into vm(host_id,user_id,image_id,template_id,ssh_key_id,created,expires,cpu,memory,disk_size,disk_id) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) returning id")
|
||||
Ok(sqlx::query("insert into vm(host_id,user_id,image_id,template_id,ssh_key_id,created,expires,cpu,memory,disk_size,disk_id,mac_address) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) returning id")
|
||||
.bind(vm.host_id)
|
||||
.bind(vm.user_id)
|
||||
.bind(vm.image_id)
|
||||
@ -218,6 +226,7 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.bind(vm.memory)
|
||||
.bind(vm.disk_size)
|
||||
.bind(vm.disk_id)
|
||||
.bind(&vm.mac_address)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)?
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::Error;
|
||||
use clap::Parser;
|
||||
use config::{Config, File};
|
||||
use fedimint_tonic_lnd::connect;
|
||||
use lnvps::api;
|
||||
@ -14,12 +15,20 @@ use log::error;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(about, version, author)]
|
||||
struct Args {
|
||||
#[clap(short, long)]
|
||||
config: Option<String>,
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
let settings: Settings = Config::builder()
|
||||
.add_source(File::with_name("config.yaml"))
|
||||
.add_source(File::with_name(&args.config.unwrap_or("config.yaml".to_string())))
|
||||
.build()?
|
||||
.try_deserialize()?;
|
||||
|
||||
|
@ -8,3 +8,4 @@ pub mod provisioner;
|
||||
pub mod settings;
|
||||
pub mod status;
|
||||
pub mod worker;
|
||||
pub mod router;
|
||||
|
@ -17,6 +17,8 @@ use lnvps_db::{
|
||||
VmPayment,
|
||||
};
|
||||
use log::{error, info, warn};
|
||||
use nostr::util::hex;
|
||||
use rand::random;
|
||||
use rand::seq::IteratorRandom;
|
||||
use reqwest::Url;
|
||||
use std::collections::HashSet;
|
||||
@ -82,17 +84,22 @@ impl Provisioner for LNVpsProvisioner {
|
||||
for image in self.db.list_os_image().await? {
|
||||
info!("Downloading image {} on {}", image.url, host.name);
|
||||
let i_name = image.filename()?;
|
||||
if files.iter().any(|v| v.vol_id.ends_with(&format!("iso/{i_name}"))) {
|
||||
if files
|
||||
.iter()
|
||||
.any(|v| v.vol_id.ends_with(&format!("iso/{i_name}")))
|
||||
{
|
||||
info!("Already downloaded, skipping");
|
||||
continue;
|
||||
}
|
||||
let t_download = client.download_image(DownloadUrlRequest {
|
||||
content: StorageContent::ISO,
|
||||
node: host.name.clone(),
|
||||
storage: iso_storage.clone(),
|
||||
url: image.url.clone(),
|
||||
filename: i_name,
|
||||
}).await?;
|
||||
let t_download = client
|
||||
.download_image(DownloadUrlRequest {
|
||||
content: StorageContent::ISO,
|
||||
node: host.name.clone(),
|
||||
storage: iso_storage.clone(),
|
||||
url: image.url.clone(),
|
||||
filename: i_name,
|
||||
})
|
||||
.await?;
|
||||
client.wait_for_task(&t_download).await?;
|
||||
}
|
||||
}
|
||||
@ -137,6 +144,12 @@ impl Provisioner for LNVpsProvisioner {
|
||||
memory: template.memory,
|
||||
disk_size: template.disk_size,
|
||||
disk_id: pick_disk.id,
|
||||
mac_address: format!(
|
||||
"bc:24:11:{}:{}:{}",
|
||||
hex::encode(&[random::<u8>()]),
|
||||
hex::encode(&[random::<u8>()]),
|
||||
hex::encode(&[random::<u8>()])
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -254,6 +267,7 @@ impl Provisioner for LNVpsProvisioner {
|
||||
vm_id,
|
||||
ip_range_id: range.id,
|
||||
ip: IpNetwork::new(ip, range_cidr.prefix())?.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let id = self.db.insert_vm_ip_assignment(&assignment).await?;
|
||||
assignment.id = id;
|
||||
@ -280,12 +294,23 @@ impl Provisioner for LNVpsProvisioner {
|
||||
ips = self.allocate_ips(vm.id).await?;
|
||||
}
|
||||
|
||||
// load ranges
|
||||
for ip in &mut ips {
|
||||
ip.hydrate_up(&self.db).await?;
|
||||
}
|
||||
|
||||
let mut ip_config = ips
|
||||
.iter()
|
||||
.map_while(|ip| {
|
||||
if let Ok(net) = ip.ip.parse::<IpNetwork>() {
|
||||
Some(match net {
|
||||
IpNetwork::V4(addr) => format!("ip={}", addr),
|
||||
IpNetwork::V4(addr) => {
|
||||
format!(
|
||||
"ip={},gw={}",
|
||||
addr,
|
||||
ip.ip_range.as_ref().map(|r| &r.gateway).unwrap()
|
||||
)
|
||||
}
|
||||
IpNetwork::V6(addr) => format!("ip6={}", addr),
|
||||
})
|
||||
} else {
|
||||
@ -305,7 +330,7 @@ impl Provisioner for LNVpsProvisioner {
|
||||
let ssh_key = self.db.get_user_ssh_key(vm.ssh_key_id).await?;
|
||||
|
||||
let mut net = vec![
|
||||
"virtio".to_string(),
|
||||
format!("virtio={}", vm.mac_address),
|
||||
format!("bridge={}", self.config.bridge),
|
||||
];
|
||||
if let Some(t) = self.config.vlan {
|
||||
|
25
src/router/mikrotik.rs
Normal file
25
src/router/mikrotik.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use std::net::IpAddr;
|
||||
use lnvps_db::VmIpAssignment;
|
||||
use rocket::async_trait;
|
||||
use crate::router::Router;
|
||||
|
||||
pub struct MikrotikRouter {
|
||||
url: String,
|
||||
token: String,
|
||||
}
|
||||
|
||||
impl MikrotikRouter {
|
||||
pub fn new(url: &str, token: &str) -> Self {
|
||||
Self {
|
||||
url: url.to_string(),
|
||||
token: token.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Router for MikrotikRouter {
|
||||
async fn add_arp_entry(&self, ip: IpAddr, mac: &[u8; 6], comment: Option<&str>) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
18
src/router/mod.rs
Normal file
18
src/router/mod.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use anyhow::Result;
|
||||
use rocket::async_trait;
|
||||
use std::net::IpAddr;
|
||||
|
||||
/// Router defines a network device used to access the hosts
|
||||
///
|
||||
/// In our infrastructure we use this to add static ARP entries on the router
|
||||
/// for every IP assignment, this way we don't need to have a ton of ARP requests on the
|
||||
/// VM network because of people doing IP scanning
|
||||
///
|
||||
/// It also prevents people from re-assigning their IP to another in the range,
|
||||
#[async_trait]
|
||||
pub trait Router {
|
||||
async fn add_arp_entry(&self, ip: IpAddr, mac: &[u8; 6], comment: Option<&str>) -> Result<()>;
|
||||
}
|
||||
|
||||
mod mikrotik;
|
||||
pub use mikrotik::*;
|
Loading…
x
Reference in New Issue
Block a user