feat: router arp entry
This commit is contained in:
parent
7ffff1e698
commit
81b233a047
@ -6,6 +6,9 @@ edition = "2021"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "api"
|
name = "api"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["mikrotik"]
|
||||||
|
mikrotik = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lnvps_db = { path = "lnvps_db" }
|
lnvps_db = { path = "lnvps_db" }
|
||||||
@ -20,7 +23,7 @@ serde_json = "1.0.132"
|
|||||||
rocket = { version = "0.5.1", features = ["json"] }
|
rocket = { version = "0.5.1", features = ["json"] }
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
nostr = { version = "0.37.0", default-features = false, features = ["std"] }
|
nostr = { version = "0.37.0", default-features = false, features = ["std"] }
|
||||||
base64 = "0.22.1"
|
base64 = { version = "0.22.1", features = ["alloc"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
fedimint-tonic-lnd = { version = "0.2.0", default-features = false, features = ["invoicesrpc"] }
|
fedimint-tonic-lnd = { version = "0.2.0", default-features = false, features = ["invoicesrpc"] }
|
||||||
ipnetwork = "0.20.0"
|
ipnetwork = "0.20.0"
|
||||||
|
@ -50,11 +50,12 @@ async fn main() -> Result<(), Error> {
|
|||||||
db.execute(setup_script).await?;
|
db.execute(setup_script).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let router = settings.router.as_ref().map(|r| r.get_router());
|
||||||
let status = VmStateCache::new();
|
let status = VmStateCache::new();
|
||||||
let worker_provisioner =
|
let worker_provisioner =
|
||||||
settings
|
settings
|
||||||
.provisioner
|
.provisioner
|
||||||
.get_provisioner(db.clone(), lnd.clone(), exchange.clone());
|
.get_provisioner(db.clone(), router, lnd.clone(), exchange.clone());
|
||||||
worker_provisioner.init().await?;
|
worker_provisioner.init().await?;
|
||||||
|
|
||||||
let mut worker = Worker::new(db.clone(), worker_provisioner, &settings, status.clone());
|
let mut worker = Worker::new(db.clone(), worker_provisioner, &settings, status.clone());
|
||||||
@ -110,10 +111,11 @@ async fn main() -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let router = settings.router.as_ref().map(|r| r.get_router());
|
||||||
let provisioner =
|
let provisioner =
|
||||||
settings
|
settings
|
||||||
.provisioner
|
.provisioner
|
||||||
.get_provisioner(db.clone(), lnd.clone(), exchange.clone());
|
.get_provisioner(db.clone(), router, lnd.clone(), exchange.clone());
|
||||||
|
|
||||||
let db: Box<dyn LNVpsDb> = Box::new(db.clone());
|
let db: Box<dyn LNVpsDb> = Box::new(db.clone());
|
||||||
let pv: Box<dyn Provisioner> = Box::new(provisioner);
|
let pv: Box<dyn Provisioner> = Box::new(provisioner);
|
||||||
|
@ -282,10 +282,10 @@ impl ProxmoxClient {
|
|||||||
body: R,
|
body: R,
|
||||||
) -> Result<T> {
|
) -> Result<T> {
|
||||||
let body = serde_json::to_string(&body)?;
|
let body = serde_json::to_string(&body)?;
|
||||||
debug!("{}", &body);
|
debug!(">> {} {}: {}", method.clone(), path, &body);
|
||||||
let rsp = self
|
let rsp = self
|
||||||
.client
|
.client
|
||||||
.request(method, self.base.join(path)?)
|
.request(method.clone(), self.base.join(path)?)
|
||||||
.header("Authorization", format!("PVEAPIToken={}", self.token))
|
.header("Authorization", format!("PVEAPIToken={}", self.token))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
@ -299,7 +299,7 @@ impl ProxmoxClient {
|
|||||||
if status.is_success() {
|
if status.is_success() {
|
||||||
Ok(serde_json::from_str(&text)?)
|
Ok(serde_json::from_str(&text)?)
|
||||||
} else {
|
} else {
|
||||||
bail!("{}", status);
|
bail!("{} {}: {}", method, path, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -579,5 +579,5 @@ pub struct VmConfig {
|
|||||||
pub kvm: Option<bool>,
|
pub kvm: Option<bool>,
|
||||||
#[serde(rename = "serial0")]
|
#[serde(rename = "serial0")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub serial_0: Option<String>
|
pub serial_0: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use base64::prelude::BASE64_STANDARD;
|
use base64::prelude::BASE64_STANDARD;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use log::{debug, info};
|
use log::debug;
|
||||||
use nostr::{Event, JsonUtil, Kind, Timestamp};
|
use nostr::{Event, JsonUtil, Kind, Timestamp};
|
||||||
use rocket::http::uri::{Absolute, Uri};
|
use rocket::http::uri::{Absolute, Uri};
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
|
@ -5,6 +5,7 @@ use crate::host::proxmox::{
|
|||||||
VmConfig,
|
VmConfig,
|
||||||
};
|
};
|
||||||
use crate::provisioner::Provisioner;
|
use crate::provisioner::Provisioner;
|
||||||
|
use crate::router::Router;
|
||||||
use crate::settings::{QemuConfig, SshConfig};
|
use crate::settings::{QemuConfig, SshConfig};
|
||||||
use crate::ssh_client::SshClient;
|
use crate::ssh_client::SshClient;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@ -15,7 +16,7 @@ use fedimint_tonic_lnd::Client;
|
|||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use lnvps_db::hydrate::Hydrate;
|
use lnvps_db::hydrate::Hydrate;
|
||||||
use lnvps_db::{IpRange, LNVpsDb, Vm, VmCostPlanIntervalType, VmIpAssignment, VmPayment};
|
use lnvps_db::{IpRange, LNVpsDb, Vm, VmCostPlanIntervalType, VmIpAssignment, VmPayment};
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
use nostr::util::hex;
|
use nostr::util::hex;
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
@ -27,6 +28,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
pub struct LNVpsProvisioner {
|
pub struct LNVpsProvisioner {
|
||||||
db: Box<dyn LNVpsDb>,
|
db: Box<dyn LNVpsDb>,
|
||||||
|
router: Option<Box<dyn Router>>,
|
||||||
lnd: Client,
|
lnd: Client,
|
||||||
rates: ExchangeRateCache,
|
rates: ExchangeRateCache,
|
||||||
read_only: bool,
|
read_only: bool,
|
||||||
@ -40,11 +42,13 @@ impl LNVpsProvisioner {
|
|||||||
config: QemuConfig,
|
config: QemuConfig,
|
||||||
ssh: Option<SshConfig>,
|
ssh: Option<SshConfig>,
|
||||||
db: impl LNVpsDb + 'static,
|
db: impl LNVpsDb + 'static,
|
||||||
|
router: Option<impl Router + 'static>,
|
||||||
lnd: Client,
|
lnd: Client,
|
||||||
rates: ExchangeRateCache,
|
rates: ExchangeRateCache,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
db: Box::new(db),
|
db: Box::new(db),
|
||||||
|
router: router.map(|r| Box::new(r) as Box<dyn Router>),
|
||||||
lnd,
|
lnd,
|
||||||
rates,
|
rates,
|
||||||
config,
|
config,
|
||||||
@ -265,6 +269,12 @@ impl Provisioner for LNVpsProvisioner {
|
|||||||
ip: ip_net.to_string(),
|
ip: ip_net.to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// add arp entry for router
|
||||||
|
if let Some(r) = self.router.as_ref() {
|
||||||
|
r.add_arp_entry(ip, &vm.mac_address, Some(&format!("VM{}", vm.id)))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
let id = self.db.insert_vm_ip_assignment(&assignment).await?;
|
let id = self.db.insert_vm_ip_assignment(&assignment).await?;
|
||||||
assignment.id = id;
|
assignment.id = id;
|
||||||
|
|
||||||
@ -274,6 +284,10 @@ impl Provisioner for LNVpsProvisioner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ret.is_empty() {
|
||||||
|
bail!("No ip ranges found in this region");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,6 +465,21 @@ impl Provisioner for LNVpsProvisioner {
|
|||||||
let j_stop = client.stop_vm(&host.name, vm.id + 100).await?;
|
let j_stop = client.stop_vm(&host.name, vm.id + 100).await?;
|
||||||
client.wait_for_task(&j_stop).await?;
|
client.wait_for_task(&j_stop).await?;
|
||||||
|
|
||||||
|
if let Some(r) = self.router.as_ref() {
|
||||||
|
let ent = r.list_arp_entry().await?;
|
||||||
|
if let Some(ent) = ent.iter().find(|e| {
|
||||||
|
e.mac_address
|
||||||
|
.as_ref()
|
||||||
|
.map(|m| m.eq_ignore_ascii_case(&vm.mac_address))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}) {
|
||||||
|
r.remove_arp_entry(ent.id.as_ref().unwrap().as_str())
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
warn!("ARP entry not found, skipping")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.db.delete_vm_ip_assignment(vm.id).await?;
|
self.db.delete_vm_ip_assignment(vm.id).await?;
|
||||||
self.db.delete_vm(vm.id).await?;
|
self.db.delete_vm(vm.id).await?;
|
||||||
|
|
||||||
|
@ -1,29 +1,101 @@
|
|||||||
use crate::router::Router;
|
use crate::router::{ArpEntry, Router};
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use base64::engine::general_purpose::STANDARD;
|
||||||
|
use base64::Engine;
|
||||||
|
use log::debug;
|
||||||
|
use reqwest::{Client, Method, Url};
|
||||||
use rocket::async_trait;
|
use rocket::async_trait;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
pub struct MikrotikRouter {
|
pub struct MikrotikRouter {
|
||||||
url: String,
|
url: Url,
|
||||||
token: String,
|
username: String,
|
||||||
|
password: String,
|
||||||
|
client: Client,
|
||||||
|
arp_interface: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MikrotikRouter {
|
impl MikrotikRouter {
|
||||||
pub fn new(url: &str, token: &str) -> Self {
|
pub fn new(url: &str, username: &str, password: &str, arp_interface: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
url: url.to_string(),
|
url: url.parse().unwrap(),
|
||||||
token: token.to_string(),
|
username: username.to_string(),
|
||||||
|
password: password.to_string(),
|
||||||
|
client: Client::builder()
|
||||||
|
.danger_accept_invalid_certs(true)
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
arp_interface: arp_interface.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn req<T: DeserializeOwned, R: Serialize>(
|
||||||
|
&self,
|
||||||
|
method: Method,
|
||||||
|
path: &str,
|
||||||
|
body: R,
|
||||||
|
) -> Result<T> {
|
||||||
|
let body = serde_json::to_string(&body)?;
|
||||||
|
debug!(">> {} {}: {}", method.clone(), path, &body);
|
||||||
|
let rsp = self
|
||||||
|
.client
|
||||||
|
.request(method.clone(), self.url.join(path)?)
|
||||||
|
.header(
|
||||||
|
"Authorization",
|
||||||
|
format!(
|
||||||
|
"Basic {}",
|
||||||
|
STANDARD.encode(format!("{}:{}", self.username, self.password))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.body(body)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
let status = rsp.status();
|
||||||
|
let text = rsp.text().await?;
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
debug!("<< {}", text);
|
||||||
|
if status.is_success() {
|
||||||
|
Ok(serde_json::from_str(&text)?)
|
||||||
|
} else {
|
||||||
|
bail!("{} {}: {}", method, path, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Router for MikrotikRouter {
|
impl Router for MikrotikRouter {
|
||||||
async fn add_arp_entry(
|
async fn list_arp_entry(&self) -> Result<Vec<ArpEntry>> {
|
||||||
&self,
|
let rsp: Vec<ArpEntry> = self.req(Method::GET, "/rest/ip/arp", ()).await?;
|
||||||
ip: IpAddr,
|
Ok(rsp)
|
||||||
mac: &[u8; 6],
|
}
|
||||||
comment: Option<&str>,
|
|
||||||
) -> anyhow::Result<()> {
|
async fn add_arp_entry(&self, ip: IpAddr, mac: &str, comment: Option<&str>) -> Result<()> {
|
||||||
todo!()
|
let _rsp: ArpEntry = self
|
||||||
|
.req(
|
||||||
|
Method::PUT,
|
||||||
|
"/rest/ip/arp",
|
||||||
|
ArpEntry {
|
||||||
|
address: ip.to_string(),
|
||||||
|
mac_address: Some(mac.to_string()),
|
||||||
|
interface: self.arp_interface.to_string(),
|
||||||
|
comment: comment.map(|c| c.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_arp_entry(&self, id: &str) -> Result<()> {
|
||||||
|
let _rsp: ArpEntry = self
|
||||||
|
.req(Method::DELETE, &format!("/rest/ip/arp/{id}"), ())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rocket::async_trait;
|
use rocket::async_trait;
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
/// Router defines a network device used to access the hosts
|
/// Router defines a network device used to access the hosts
|
||||||
@ -10,9 +11,27 @@ use std::net::IpAddr;
|
|||||||
///
|
///
|
||||||
/// It also prevents people from re-assigning their IP to another in the range,
|
/// It also prevents people from re-assigning their IP to another in the range,
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Router {
|
pub trait Router: Send + Sync {
|
||||||
async fn add_arp_entry(&self, ip: IpAddr, mac: &[u8; 6], comment: Option<&str>) -> Result<()>;
|
async fn list_arp_entry(&self) -> Result<Vec<ArpEntry>>;
|
||||||
|
async fn add_arp_entry(&self, ip: IpAddr, mac: &str, comment: Option<&str>) -> Result<()>;
|
||||||
|
async fn remove_arp_entry(&self, id: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct ArpEntry {
|
||||||
|
#[serde(rename = ".id")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub id: Option<String>,
|
||||||
|
pub address: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[serde(rename = "mac-address")]
|
||||||
|
pub mac_address: Option<String>,
|
||||||
|
pub interface: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mikrotik")]
|
||||||
mod mikrotik;
|
mod mikrotik;
|
||||||
|
#[cfg(feature = "mikrotik")]
|
||||||
pub use mikrotik::*;
|
pub use mikrotik::*;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::exchange::ExchangeRateCache;
|
use crate::exchange::ExchangeRateCache;
|
||||||
use crate::provisioner::lnvps::LNVpsProvisioner;
|
use crate::provisioner::lnvps::LNVpsProvisioner;
|
||||||
use crate::provisioner::Provisioner;
|
use crate::provisioner::Provisioner;
|
||||||
|
use crate::router::{MikrotikRouter, Router};
|
||||||
use fedimint_tonic_lnd::Client;
|
use fedimint_tonic_lnd::Client;
|
||||||
use lnvps_db::LNVpsDb;
|
use lnvps_db::LNVpsDb;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -20,6 +21,9 @@ pub struct Settings {
|
|||||||
|
|
||||||
/// SMTP settings for sending emails
|
/// SMTP settings for sending emails
|
||||||
pub smtp: Option<SmtpConfig>,
|
pub smtp: Option<SmtpConfig>,
|
||||||
|
|
||||||
|
/// Network router config
|
||||||
|
pub router: Option<RouterConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
@ -29,6 +33,18 @@ pub struct LndConfig {
|
|||||||
pub macaroon: PathBuf,
|
pub macaroon: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub enum RouterConfig {
|
||||||
|
Mikrotik {
|
||||||
|
url: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
|
||||||
|
/// Interface used to add arp entries
|
||||||
|
arp_interface: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct SmtpConfig {
|
pub struct SmtpConfig {
|
||||||
/// Admin user id, for sending system notifications
|
/// Admin user id, for sending system notifications
|
||||||
@ -92,6 +108,7 @@ impl ProvisionerConfig {
|
|||||||
pub fn get_provisioner(
|
pub fn get_provisioner(
|
||||||
&self,
|
&self,
|
||||||
db: impl LNVpsDb + 'static,
|
db: impl LNVpsDb + 'static,
|
||||||
|
router: Option<impl Router + 'static>,
|
||||||
lnd: Client,
|
lnd: Client,
|
||||||
exchange: ExchangeRateCache,
|
exchange: ExchangeRateCache,
|
||||||
) -> impl Provisioner + 'static {
|
) -> impl Provisioner + 'static {
|
||||||
@ -100,7 +117,28 @@ impl ProvisionerConfig {
|
|||||||
qemu,
|
qemu,
|
||||||
ssh,
|
ssh,
|
||||||
read_only,
|
read_only,
|
||||||
} => LNVpsProvisioner::new(*read_only, qemu.clone(), ssh.clone(), db, lnd, exchange),
|
} => LNVpsProvisioner::new(
|
||||||
|
*read_only,
|
||||||
|
qemu.clone(),
|
||||||
|
ssh.clone(),
|
||||||
|
db,
|
||||||
|
router,
|
||||||
|
lnd,
|
||||||
|
exchange,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RouterConfig {
|
||||||
|
pub fn get_router(&self) -> impl Router + 'static {
|
||||||
|
match self {
|
||||||
|
RouterConfig::Mikrotik {
|
||||||
|
url,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
arp_interface,
|
||||||
|
} => MikrotikRouter::new(&url, &username, &password, &arp_interface),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,11 @@ use crate::provisioner::Provisioner;
|
|||||||
use crate::settings::{Settings, SmtpConfig};
|
use crate::settings::{Settings, SmtpConfig};
|
||||||
use crate::status::{VmRunningState, VmState, VmStateCache};
|
use crate::status::{VmRunningState, VmState, VmStateCache};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::{DateTime, Days, Utc};
|
use chrono::{Days, Utc};
|
||||||
use lettre::message::MessageBuilder;
|
use lettre::message::MessageBuilder;
|
||||||
use lettre::transport::smtp::authentication::Credentials;
|
use lettre::transport::smtp::authentication::Credentials;
|
||||||
use lettre::transport::smtp::SmtpTransportBuilder;
|
|
||||||
use lettre::AsyncTransport;
|
use lettre::AsyncTransport;
|
||||||
use lettre::{AsyncSmtpTransport, SmtpTransport, Tokio1Executor, Transport};
|
use lettre::{AsyncSmtpTransport, Tokio1Executor, Transport};
|
||||||
use lnvps_db::LNVpsDb;
|
use lnvps_db::LNVpsDb;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use rocket::futures::SinkExt;
|
use rocket::futures::SinkExt;
|
||||||
@ -118,11 +117,16 @@ impl Worker {
|
|||||||
{
|
{
|
||||||
info!("Deleting expired VM {}", db_vm.id);
|
info!("Deleting expired VM {}", db_vm.id);
|
||||||
self.provisioner.delete_vm(db_vm.id).await?;
|
self.provisioner.delete_vm(db_vm.id).await?;
|
||||||
|
let title = Some(format!("[VM{}] Deleted", db_vm.id));
|
||||||
self.tx.send(WorkJob::SendNotification {
|
self.tx.send(WorkJob::SendNotification {
|
||||||
user_id: db_vm.user_id,
|
user_id: db_vm.user_id,
|
||||||
title: Some(format!("[VM{}] Deleted", db_vm.id)),
|
title: title.clone(),
|
||||||
message: format!("Your VM #{} has been deleted!", db_vm.id),
|
message: format!("Your VM #{} has been deleted!", db_vm.id),
|
||||||
})?;
|
})?;
|
||||||
|
self.queue_admin_notification(
|
||||||
|
format!("VM{} is ready for deletion", db_vm.id),
|
||||||
|
title,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +226,7 @@ impl Worker {
|
|||||||
}
|
}
|
||||||
let msg = b.body(message)?;
|
let msg = b.body(message)?;
|
||||||
|
|
||||||
let mut sender = AsyncSmtpTransport::<Tokio1Executor>::relay(&smtp.server)?
|
let sender = AsyncSmtpTransport::<Tokio1Executor>::relay(&smtp.server)?
|
||||||
.credentials(Credentials::new(
|
.credentials(Credentials::new(
|
||||||
smtp.username.to_string(),
|
smtp.username.to_string(),
|
||||||
smtp.password.to_string(),
|
smtp.password.to_string(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user