more stuff
This commit is contained in:
parent
27a0f247a4
commit
e7113d6772
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -97,7 +97,7 @@ dependencies = [
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"clap 2.34.0",
|
"clap 2.34.0",
|
||||||
"env_logger",
|
"env_logger 0.9.3",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lazycell",
|
"lazycell",
|
||||||
"log",
|
"log",
|
||||||
|
@ -472,6 +472,19 @@ dependencies = [
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
|
||||||
|
dependencies = [
|
||||||
|
"humantime",
|
||||||
|
"is-terminal",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -1057,8 +1070,11 @@ name = "nzr"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 4.0.29",
|
"clap 4.0.29",
|
||||||
|
"env_logger 0.10.0",
|
||||||
"home",
|
"home",
|
||||||
|
"log",
|
||||||
"nzr-api",
|
"nzr-api",
|
||||||
|
"serde_json",
|
||||||
"tabled",
|
"tabled",
|
||||||
"tarpc",
|
"tarpc",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub struct NewInstance {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub interface: String,
|
pub subnet: String,
|
||||||
pub base_image: String,
|
pub base_image: String,
|
||||||
pub cores: u8,
|
pub cores: u8,
|
||||||
pub memory: u32,
|
pub memory: u32,
|
||||||
|
|
|
@ -55,7 +55,7 @@ impl Default for Config {
|
||||||
socket_path: PathBuf::from("/var/run/nazrin/nzrd.sock"),
|
socket_path: PathBuf::from("/var/run/nazrin/nzrd.sock"),
|
||||||
admin_group: None,
|
admin_group: None,
|
||||||
},
|
},
|
||||||
db_path: PathBuf::from("/var/run/nazrin/nzr.db"),
|
db_path: PathBuf::from("/var/lib/nazrin/nzr.db"),
|
||||||
libvirt_uri: match std::env::var("LIBVIRT_URI") {
|
libvirt_uri: match std::env::var("LIBVIRT_URI") {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => String::from("qemu:///system"),
|
Err(_) => String::from("qemu:///system"),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use model::{Instance, Subnet};
|
use model::{CreateStatus, Instance, Subnet};
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
@ -10,14 +10,16 @@ pub use trust_dns_proto;
|
||||||
#[tarpc::service]
|
#[tarpc::service]
|
||||||
pub trait Nazrin {
|
pub trait Nazrin {
|
||||||
/// Creates a new instance.
|
/// Creates a new instance.
|
||||||
async fn new_instance(build_args: args::NewInstance) -> Result<Instance, String>;
|
async fn new_instance(build_args: args::NewInstance) -> Result<uuid::Uuid, String>;
|
||||||
|
/// Poll for the current status of an instance being created.
|
||||||
|
async fn poll_new_instance(task_id: uuid::Uuid) -> Option<CreateStatus>;
|
||||||
/// Deletes an existing instance.
|
/// Deletes an existing instance.
|
||||||
///
|
///
|
||||||
/// This should involve deleting all related disks and clearing
|
/// This should involve deleting all related disks and clearing
|
||||||
/// the lease information from the subnet data, if any.
|
/// the lease information from the subnet data, if any.
|
||||||
async fn delete_instance(name: String) -> Result<(), String>;
|
async fn delete_instance(name: String) -> Result<(), String>;
|
||||||
/// Gets a list of existing instances.
|
/// Gets a list of existing instances.
|
||||||
async fn get_instances() -> Result<Vec<Instance>, String>;
|
async fn get_instances(with_status: bool) -> Result<Vec<Instance>, String>;
|
||||||
/// Cleans up unusable entries in the database.
|
/// Cleans up unusable entries in the database.
|
||||||
async fn garbage_collect() -> Result<(), String>;
|
async fn garbage_collect() -> Result<(), String>;
|
||||||
/// Creates a new subnet.
|
/// Creates a new subnet.
|
||||||
|
@ -26,6 +28,8 @@ pub trait Nazrin {
|
||||||
/// interfaces they reference. This should be used primarily for
|
/// interfaces they reference. This should be used primarily for
|
||||||
/// ease-of-use and bookkeeping (e.g., assigning dynamic leases).
|
/// ease-of-use and bookkeeping (e.g., assigning dynamic leases).
|
||||||
async fn new_subnet(build_args: Subnet) -> Result<Subnet, String>;
|
async fn new_subnet(build_args: Subnet) -> Result<Subnet, String>;
|
||||||
|
/// Modifies an existing subnet.
|
||||||
|
async fn modify_subnet(edit_args: Subnet) -> Result<Subnet, String>;
|
||||||
/// Gets a list of existing subnets.
|
/// Gets a list of existing subnets.
|
||||||
async fn get_subnets() -> Result<Vec<Subnet>, String>;
|
async fn get_subnets() -> Result<Vec<Subnet>, String>;
|
||||||
/// Deletes an existing subnet.
|
/// Deletes an existing subnet.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt, net::Ipv4Addr, str::FromStr};
|
use std::{fmt, net::Ipv4Addr};
|
||||||
use trust_dns_proto::rr::Name;
|
use trust_dns_proto::rr::Name;
|
||||||
|
|
||||||
use crate::net::{cidr::CidrV4, mac::MacAddr};
|
use crate::net::{cidr::CidrV4, mac::MacAddr};
|
||||||
|
@ -60,6 +60,13 @@ impl fmt::Display for DomainState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CreateStatus {
|
||||||
|
pub status_text: String,
|
||||||
|
pub completion: f32,
|
||||||
|
pub result: Option<Result<Instance, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Struct representing a VM instance.
|
/// Struct representing a VM instance.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
|
@ -72,6 +79,8 @@ pub struct Instance {
|
||||||
/// Struct representing a logical "lease" held by a VM.
|
/// Struct representing a logical "lease" held by a VM.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Lease {
|
pub struct Lease {
|
||||||
|
/// Subnet name corresponding to the lease
|
||||||
|
pub subnet: String,
|
||||||
/// The IPv4 address held by the Lease
|
/// The IPv4 address held by the Lease
|
||||||
pub addr: CidrV4,
|
pub addr: CidrV4,
|
||||||
/// The MAC address associated by the Lease
|
/// The MAC address associated by the Lease
|
||||||
|
@ -80,15 +89,17 @@ pub struct Lease {
|
||||||
|
|
||||||
/// Struct representing a subnet used by the host for virtual
|
/// Struct representing a subnet used by the host for virtual
|
||||||
/// networking.
|
/// networking.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Subnet {
|
pub struct Subnet {
|
||||||
/// The name of the interface the subnet is accessible via.
|
/// The subnet short name.
|
||||||
pub ifname: IfaceStr,
|
pub name: String,
|
||||||
pub data: SubnetData,
|
pub data: SubnetData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct SubnetData {
|
pub struct SubnetData {
|
||||||
|
/// The name of the interface the subnet is accessible via.
|
||||||
|
pub ifname: String,
|
||||||
/// The network information for the subnet.
|
/// The network information for the subnet.
|
||||||
pub network: CidrV4,
|
pub network: CidrV4,
|
||||||
/// The first host address that can be assigned dynamically
|
/// The first host address that can be assigned dynamically
|
||||||
|
@ -98,65 +109,21 @@ pub struct SubnetData {
|
||||||
/// on the subnet.
|
/// on the subnet.
|
||||||
pub end_host: Ipv4Addr,
|
pub end_host: Ipv4Addr,
|
||||||
/// The default gateway for the subnet.
|
/// The default gateway for the subnet.
|
||||||
pub gateway4: Option<Ipv4Addr>,
|
pub gateway4: Ipv4Addr,
|
||||||
/// The primary DNS server for the subnet.
|
/// The primary DNS server for the subnet.
|
||||||
pub dns: Vec<Ipv4Addr>,
|
pub dns: Vec<Ipv4Addr>,
|
||||||
/// The base domain used for DNS lookup.
|
/// The base domain used for DNS lookup.
|
||||||
pub domain_name: Option<Name>,
|
pub domain_name: Option<Name>,
|
||||||
|
/// The VLAN ID used for the domain. If none, no VLAN is set.
|
||||||
|
pub vlan_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper struct for [u8; 16], representing the maximum length
|
impl SubnetData {
|
||||||
/// for an interface's name.
|
pub fn start_bytes(&self) -> u32 {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
self.network.host_bits(&self.start_host)
|
||||||
pub struct IfaceStr {
|
|
||||||
name: [u8; 16],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<[u8]> for IfaceStr {
|
pub fn end_bytes(&self) -> u32 {
|
||||||
fn as_ref(&self) -> &[u8] {
|
self.network.host_bits(&self.end_host)
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum ParseError {
|
|
||||||
BadSize(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ParseError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "Interface name must be at most 15 characters")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for ParseError {}
|
|
||||||
|
|
||||||
impl FromStr for IfaceStr {
|
|
||||||
type Err = ParseError;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
if s.bytes().len() > 15 {
|
|
||||||
Err(Self::Err::BadSize(s.to_owned()))
|
|
||||||
} else {
|
|
||||||
Ok(IfaceStr {
|
|
||||||
name: {
|
|
||||||
let mut ifstr = [0u8; 16];
|
|
||||||
let bytes = s.as_bytes();
|
|
||||||
ifstr[..bytes.len()].copy_from_slice(&bytes[..bytes.len()]);
|
|
||||||
ifstr
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for IfaceStr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
std::str::from_utf8(&self.name)
|
|
||||||
.map(|a| a.trim_end_matches(char::from(0)))
|
|
||||||
.map_err(|_| fmt::Error)?
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,14 @@ impl<'de> Deserialize<'de> for MacAddr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Index<usize> for MacAddr {
|
||||||
|
type Output = u8;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.octets[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
ParseError(std::num::ParseIntError),
|
ParseError(std::num::ParseIntError),
|
||||||
|
@ -76,6 +84,10 @@ impl MacAddr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn invalid() -> MacAddr {
|
||||||
|
MacAddr { octets: [0u8; 6] }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_bytes<T>(value: T) -> Result<MacAddr, Error>
|
pub fn from_bytes<T>(value: T) -> Result<MacAddr, Error>
|
||||||
where
|
where
|
||||||
T: AsRef<[u8]>,
|
T: AsRef<[u8]>,
|
||||||
|
|
|
@ -13,3 +13,6 @@ tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||||
tokio-serde = { version = "0.8.0", features = ["bincode"] }
|
tokio-serde = { version = "0.8.0", features = ["bincode"] }
|
||||||
tarpc = { version = "0.31", features = ["tokio1", "unix", "serde-transport", "serde-transport-bincode"] }
|
tarpc = { version = "0.31", features = ["tokio1", "unix", "serde-transport", "serde-transport-bincode"] }
|
||||||
tabled = "0.10.0"
|
tabled = "0.10.0"
|
||||||
|
serde_json = "1"
|
||||||
|
log = "0.4.17"
|
||||||
|
env_logger = "0.10.0"
|
|
@ -1,4 +1,4 @@
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{CommandFactory, FromArgMatches, Parser, Subcommand};
|
||||||
use nzr_api::model;
|
use nzr_api::model;
|
||||||
use nzr_api::net::cidr::CidrV4;
|
use nzr_api::net::cidr::CidrV4;
|
||||||
use nzr_api::trust_dns_proto::rr::Name;
|
use nzr_api::trust_dns_proto::rr::Name;
|
||||||
|
@ -16,9 +16,9 @@ mod table;
|
||||||
pub struct NewInstanceArgs {
|
pub struct NewInstanceArgs {
|
||||||
/// Name of the instance to be created
|
/// Name of the instance to be created
|
||||||
name: String,
|
name: String,
|
||||||
/// Bridge the instance will initially run on
|
/// Subnet the instance will initially run on
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
interface: String,
|
subnet: String,
|
||||||
/// Long description of the instance
|
/// Long description of the instance
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
@ -52,10 +52,18 @@ enum InstanceCmd {
|
||||||
List,
|
List,
|
||||||
/// Deletes all invalid instances from the database
|
/// Deletes all invalid instances from the database
|
||||||
Prune,
|
Prune,
|
||||||
|
/// Shows information on an instance
|
||||||
|
Dump {
|
||||||
|
name: Option<String>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
quick: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, clap::Args)]
|
#[derive(Debug, clap::Args)]
|
||||||
pub struct AddNetArgs {
|
pub struct AddNetArgs {
|
||||||
|
/// Short name for the subnet (e.g., `servers')
|
||||||
|
pub name: String,
|
||||||
/// Name of the bridge interface VMs will attach to
|
/// Name of the bridge interface VMs will attach to
|
||||||
pub interface: String,
|
pub interface: String,
|
||||||
/// Subnet associated with the bridge interface, in CIDR notation (x.x.x.x/y)
|
/// Subnet associated with the bridge interface, in CIDR notation (x.x.x.x/y)
|
||||||
|
@ -74,19 +82,47 @@ pub struct AddNetArgs {
|
||||||
pub end_addr: Option<std::net::Ipv4Addr>,
|
pub end_addr: Option<std::net::Ipv4Addr>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub domain_name: Option<Name>,
|
pub domain_name: Option<Name>,
|
||||||
|
/// VLAN ID for the VM, if any
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub vlan_id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Args)]
|
||||||
|
pub struct EditNetArgs {
|
||||||
|
/// Short name of the subnet
|
||||||
|
pub name: String,
|
||||||
|
/// Default gateway for the subnet
|
||||||
|
#[arg(long)]
|
||||||
|
pub gateway: Option<std::net::Ipv4Addr>,
|
||||||
|
/// Default DNS address for the subnet
|
||||||
|
#[arg(long)]
|
||||||
|
pub dns_server: Option<std::net::Ipv4Addr>,
|
||||||
|
/// Start address for IP assignment
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub start_addr: Option<std::net::Ipv4Addr>,
|
||||||
|
/// End address for IP assignment
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub end_addr: Option<std::net::Ipv4Addr>,
|
||||||
|
/// Domain name associated with the subnet
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub domain_name: Option<Name>,
|
||||||
|
/// VLAN ID for the VM, if any
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub vlan_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
enum NetCmd {
|
enum NetCmd {
|
||||||
/// Add a new network to the database
|
/// Add a new network to the database
|
||||||
Add(AddNetArgs),
|
Add(AddNetArgs),
|
||||||
|
/// Edit an existing network
|
||||||
|
Edit(EditNetArgs),
|
||||||
/// List all networks in the database
|
/// List all networks in the database
|
||||||
List,
|
List,
|
||||||
/// Delete a network from the database
|
/// Delete a network from the database
|
||||||
Delete {
|
Delete { name: String },
|
||||||
#[arg(short, long)]
|
/// Shows information on a subnet
|
||||||
interface: String,
|
Dump { name: Option<String> },
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
|
@ -160,26 +196,44 @@ impl CommandError {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_command() -> Result<(), Box<dyn std::error::Error>> {
|
async fn handle_command() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cli = Args::parse();
|
env_logger::init();
|
||||||
|
|
||||||
|
let mut matches = Args::command().infer_subcommands(true).get_matches();
|
||||||
|
let cli = Args::from_arg_matches_mut(&mut matches)?;
|
||||||
let config: config::Config = nzr_api::config::Config::figment().extract()?;
|
let config: config::Config = nzr_api::config::Config::figment().extract()?;
|
||||||
let conn = UnixStream::connect(&config.rpc.socket_path).await?;
|
let conn = UnixStream::connect(&config.rpc.socket_path).await?;
|
||||||
let codec_builder = LengthDelimitedCodec::builder();
|
let framed_io = LengthDelimitedCodec::builder()
|
||||||
let transport = tarpc::serde_transport::new(codec_builder.new_framed(conn), Bincode::default());
|
.length_field_type::<u32>()
|
||||||
|
.new_framed(conn);
|
||||||
|
let transport = tarpc::serde_transport::new(framed_io, Bincode::default());
|
||||||
let client = NazrinClient::new(Default::default(), transport).spawn();
|
let client = NazrinClient::new(Default::default(), transport).spawn();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Instance { command } => {
|
Commands::Instance { command } => match command {
|
||||||
match command {
|
InstanceCmd::Dump { name, quick } => {
|
||||||
|
let instances = (client
|
||||||
|
.get_instances(tarpc::context::current(), !quick)
|
||||||
|
.await?)?;
|
||||||
|
if let Some(name) = name {
|
||||||
|
if let Some(inst) = instances.iter().find(|f| f.name == name) {
|
||||||
|
println!("{}", serde_json::to_string(inst)?);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("{}", serde_json::to_string(&instances)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
InstanceCmd::New(args) => {
|
InstanceCmd::New(args) => {
|
||||||
let ssh_keys: Vec<String> = {
|
let ssh_keys: Vec<String> = {
|
||||||
let key_file =
|
let key_file = args.sshkey_file.map_or_else(
|
||||||
args.sshkey_file.map_or_else(
|
|
||||||
|| {
|
|| {
|
||||||
home::home_dir().map_or_else(|| {
|
home::home_dir().map_or_else(
|
||||||
Err(CommandError::from("SSH keyfile not defined, and couldn't find home directory"))
|
|| {
|
||||||
}, |hd| {
|
Err(CommandError::from(
|
||||||
Ok(hd.join(".ssh/authorized_keys"))
|
"SSH keyfile not defined, and couldn't find home directory",
|
||||||
})
|
))
|
||||||
|
},
|
||||||
|
|hd| Ok(hd.join(".ssh/authorized_keys")),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
Ok,
|
Ok,
|
||||||
)?;
|
)?;
|
||||||
|
@ -205,31 +259,72 @@ async fn handle_command() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
name: args.name,
|
name: args.name,
|
||||||
title: None,
|
title: None,
|
||||||
description: args.description,
|
description: args.description,
|
||||||
interface: args.interface,
|
subnet: args.subnet,
|
||||||
base_image: args.base,
|
base_image: args.base,
|
||||||
cores: args.cores,
|
cores: args.cores,
|
||||||
memory: args.mem,
|
memory: args.mem,
|
||||||
disk_sizes: (args.primary_size, args.secondary_size),
|
disk_sizes: (args.primary_size, args.secondary_size),
|
||||||
ssh_keys,
|
ssh_keys,
|
||||||
};
|
};
|
||||||
let instance = (client
|
let task_id = (client
|
||||||
.new_instance(tarpc::context::current(), build_args)
|
.new_instance(tarpc::context::current(), build_args)
|
||||||
.await?)?;
|
.await?)?;
|
||||||
|
|
||||||
|
const MAX_RETRIES: i32 = 5;
|
||||||
|
let mut retries = 0;
|
||||||
|
let mut current_pct: f32 = 0.0;
|
||||||
|
loop {
|
||||||
|
let status = client
|
||||||
|
.poll_new_instance(tarpc::context::current(), task_id)
|
||||||
|
.await;
|
||||||
|
match status {
|
||||||
|
Ok(Some(status)) => {
|
||||||
|
if let Some(result) = status.result {
|
||||||
|
match result {
|
||||||
|
Ok(instance) => {
|
||||||
println!("Instance {} created!", &instance.name);
|
println!("Instance {} created!", &instance.name);
|
||||||
if let Some(lease) = instance.lease {
|
if let Some(lease) = instance.lease {
|
||||||
println!(
|
println!(
|
||||||
"You should be able to reach it at:\n\n ssh root@{}",
|
"You should be able to reach it with: ssh root@{}",
|
||||||
lease.addr.addr,
|
lease.addr.addr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Error while creating instance: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if status.completion != current_pct {
|
||||||
|
println!("[remote] {}", &status.status_text);
|
||||||
|
current_pct = status.completion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
log::error!("Task ID {} went AWOL??", task_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Got RPC error: {}", err);
|
||||||
|
retries += 1;
|
||||||
|
if retries >= MAX_RETRIES {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
log::error!("Retrying (attempt {}/{})...", retries, MAX_RETRIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
InstanceCmd::Delete { name } => {
|
InstanceCmd::Delete { name } => {
|
||||||
(client
|
(client
|
||||||
.delete_instance(tarpc::context::current(), name)
|
.delete_instance(tarpc::context::current(), name)
|
||||||
.await?)?;
|
.await?)?;
|
||||||
}
|
}
|
||||||
InstanceCmd::List => {
|
InstanceCmd::List => {
|
||||||
let instances = client.get_instances(tarpc::context::current()).await?;
|
let instances = client
|
||||||
|
.get_instances(tarpc::context::current(), true)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let tabular: Vec<table::Instance> =
|
let tabular: Vec<table::Instance> =
|
||||||
instances?.iter().map(table::Instance::from).collect();
|
instances?.iter().map(table::Instance::from).collect();
|
||||||
|
@ -237,31 +332,90 @@ async fn handle_command() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("{}", table.with(tabled::Style::psql()));
|
println!("{}", table.with(tabled::Style::psql()));
|
||||||
}
|
}
|
||||||
InstanceCmd::Prune => (client.garbage_collect(tarpc::context::current()).await?)?,
|
InstanceCmd::Prune => (client.garbage_collect(tarpc::context::current()).await?)?,
|
||||||
}
|
},
|
||||||
}
|
|
||||||
Commands::Net { command } => match command {
|
Commands::Net { command } => match command {
|
||||||
NetCmd::Add(args) => {
|
NetCmd::Add(args) => {
|
||||||
let net_arg = CidrV4::from_str(&args.network)?;
|
let net_arg = CidrV4::from_str(&args.network)?;
|
||||||
let build_args = model::Subnet {
|
let build_args = model::Subnet {
|
||||||
ifname: model::IfaceStr::from_str(&args.interface)?,
|
name: args.name,
|
||||||
data: model::SubnetData {
|
data: model::SubnetData {
|
||||||
|
ifname: args.interface.clone(),
|
||||||
network: net_arg.clone(),
|
network: net_arg.clone(),
|
||||||
start_host: args.start_addr.unwrap_or(net_arg.make_ip(10)?),
|
start_host: args.start_addr.unwrap_or(net_arg.make_ip(10)?),
|
||||||
end_host: args
|
end_host: args
|
||||||
.end_addr
|
.end_addr
|
||||||
.unwrap_or((u32::from(net_arg.broadcast()) - 1u32).into()),
|
.unwrap_or((u32::from(net_arg.broadcast()) - 1u32).into()),
|
||||||
gateway4: args.gateway,
|
gateway4: args.gateway.unwrap_or(net_arg.make_ip(1)?),
|
||||||
dns: args.dns_server.map_or(Vec::new(), |d| vec![d]),
|
dns: args.dns_server.map_or(Vec::new(), |d| vec![d]),
|
||||||
domain_name: args.domain_name,
|
domain_name: args.domain_name,
|
||||||
|
vlan_id: args.vlan_id,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
(client
|
(client
|
||||||
.new_subnet(tarpc::context::current(), build_args)
|
.new_subnet(tarpc::context::current(), build_args)
|
||||||
.await?)?;
|
.await?)?;
|
||||||
}
|
}
|
||||||
NetCmd::Delete { interface } => {
|
NetCmd::Edit(args) => {
|
||||||
|
let mut net = client
|
||||||
|
.get_subnets(tarpc::context::current())
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.and_then(|res| {
|
||||||
|
res?.iter()
|
||||||
|
.find_map(|ent| {
|
||||||
|
if ent.name == args.name {
|
||||||
|
Some(ent.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or_else(|| format!("Couldn't find network {}", &args.name))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// merge in the new args
|
||||||
|
if let Some(gateway) = args.gateway {
|
||||||
|
net.data.gateway4 = gateway;
|
||||||
|
}
|
||||||
|
if let Some(dns_server) = args.dns_server {
|
||||||
|
net.data.dns = vec![dns_server]
|
||||||
|
}
|
||||||
|
if let Some(start_addr) = args.start_addr {
|
||||||
|
net.data.start_host = start_addr;
|
||||||
|
}
|
||||||
|
if let Some(end_addr) = args.end_addr {
|
||||||
|
net.data.end_host = end_addr;
|
||||||
|
}
|
||||||
|
if let Some(domain_name) = args.domain_name {
|
||||||
|
net.data.domain_name = Some(domain_name);
|
||||||
|
}
|
||||||
|
if let Some(vlan_id) = args.vlan_id {
|
||||||
|
net.data.vlan_id = Some(vlan_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the update
|
||||||
|
client
|
||||||
|
.modify_subnet(tarpc::context::current(), net)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("RPC error: {}", err))
|
||||||
|
.and_then(|res| {
|
||||||
|
res.map(|e| {
|
||||||
|
println!("Subnet {} updated.", e.name);
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
NetCmd::Dump { name } => {
|
||||||
|
let subnets = (client.get_subnets(tarpc::context::current()).await?)?;
|
||||||
|
if let Some(name) = name {
|
||||||
|
if let Some(net) = subnets.iter().find(|s| s.name == name) {
|
||||||
|
println!("{}", serde_json::to_string(net)?);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("{}", serde_json::to_string(&subnets)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetCmd::Delete { name } => {
|
||||||
(client
|
(client
|
||||||
.delete_subnet(tarpc::context::current(), interface)
|
.delete_subnet(tarpc::context::current(), name)
|
||||||
.await?)?;
|
.await?)?;
|
||||||
}
|
}
|
||||||
NetCmd::List => {
|
NetCmd::List => {
|
||||||
|
@ -281,9 +435,9 @@ async fn handle_command() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if let Err(err) = handle_command().await {
|
if let Err(err) = handle_command().await {
|
||||||
if std::any::Any::type_id(&*err).type_id() == TypeId::of::<tarpc::client::RpcError>() {
|
if std::any::Any::type_id(&*err).type_id() == TypeId::of::<tarpc::client::RpcError>() {
|
||||||
eprintln!("[err] Error communicating with server: {}", err);
|
log::error!("Error communicating with server: {}", err);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("[err] {}", err);
|
log::error!("{}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -26,6 +26,8 @@ impl From<&model::Instance> for Instance {
|
||||||
|
|
||||||
#[derive(Tabled)]
|
#[derive(Tabled)]
|
||||||
pub struct Subnet {
|
pub struct Subnet {
|
||||||
|
#[tabled(rename = "Name")]
|
||||||
|
name: String,
|
||||||
#[tabled(rename = "Interface")]
|
#[tabled(rename = "Interface")]
|
||||||
interface: String,
|
interface: String,
|
||||||
#[tabled(rename = "Network")]
|
#[tabled(rename = "Network")]
|
||||||
|
@ -35,7 +37,8 @@ pub struct Subnet {
|
||||||
impl From<&model::Subnet> for Subnet {
|
impl From<&model::Subnet> for Subnet {
|
||||||
fn from(value: &model::Subnet) -> Self {
|
fn from(value: &model::Subnet) -> Self {
|
||||||
Self {
|
Self {
|
||||||
interface: value.ifname.to_string(),
|
name: value.name.clone(),
|
||||||
|
interface: value.data.ifname.to_string(),
|
||||||
network: value.data.network.to_string(),
|
network: value.data.network.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ctrl::net::Subnet;
|
use crate::ctrl::net::Subnet;
|
||||||
|
use crate::ctrl::Entity;
|
||||||
use crate::ctrl::Storable;
|
use crate::ctrl::Storable;
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use nzr_api::model;
|
use nzr_api::model;
|
||||||
|
@ -7,23 +8,11 @@ use nzr_api::model;
|
||||||
pub async fn add_subnet(
|
pub async fn add_subnet(
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
args: model::Subnet,
|
args: model::Subnet,
|
||||||
) -> Result<Subnet, Box<dyn std::error::Error>> {
|
) -> Result<Entity<Subnet>, Box<dyn std::error::Error>> {
|
||||||
let subnet = Subnet::new(
|
let subnet = Subnet::from_model(&args.data)
|
||||||
&args.ifname.to_string(),
|
|
||||||
&args.data.network,
|
|
||||||
&args.data.start_host,
|
|
||||||
&args.data.end_host,
|
|
||||||
args.data.gateway4.as_ref(),
|
|
||||||
&args.data.dns,
|
|
||||||
args.data.domain_name,
|
|
||||||
)
|
|
||||||
.map_err(|er| cmd_error!("Couldn't generate subnet: {}", er))?;
|
.map_err(|er| cmd_error!("Couldn't generate subnet: {}", er))?;
|
||||||
|
|
||||||
let mut ent = Subnet::insert(
|
let mut ent = Subnet::insert(ctx.db.clone(), subnet.clone(), args.name.as_bytes())?;
|
||||||
ctx.db.clone(),
|
|
||||||
subnet.clone(),
|
|
||||||
args.ifname.to_string().as_bytes(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
ent.transient = true;
|
ent.transient = true;
|
||||||
|
|
||||||
|
@ -31,7 +20,7 @@ pub async fn add_subnet(
|
||||||
Err(cmd_error!("Failed to create new DNS zone: {}", err))
|
Err(cmd_error!("Failed to create new DNS zone: {}", err))
|
||||||
} else {
|
} else {
|
||||||
ent.transient = false;
|
ent.transient = false;
|
||||||
Ok(subnet)
|
Ok(ent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use tokio::sync::RwLock;
|
||||||
use virt::stream::Stream;
|
use virt::stream::Stream;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -5,7 +6,7 @@ use crate::cloud::{DNSMeta, EtherMatch, Metadata, NetworkMeta};
|
||||||
use crate::ctrl::net::Subnet;
|
use crate::ctrl::net::Subnet;
|
||||||
use crate::ctrl::virtxml::build::DomainBuilder;
|
use crate::ctrl::virtxml::build::DomainBuilder;
|
||||||
use crate::ctrl::virtxml::{DiskDeviceType, SerialType, VolType, Volume};
|
use crate::ctrl::virtxml::{DiskDeviceType, SerialType, VolType, Volume};
|
||||||
use crate::ctrl::vm::{InstDb, Instance, InstanceError};
|
use crate::ctrl::vm::{InstDb, Instance, InstanceError, Progress};
|
||||||
use crate::ctrl::Storable;
|
use crate::ctrl::Storable;
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
@ -13,22 +14,33 @@ use crate::virt::VirtVolume;
|
||||||
use log::*;
|
use log::*;
|
||||||
use nzr_api::args;
|
use nzr_api::args;
|
||||||
use nzr_api::net::mac::MacAddr;
|
use nzr_api::net::mac::MacAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
use trust_dns_server::proto::rr::Name;
|
use trust_dns_server::proto::rr::Name;
|
||||||
|
|
||||||
const VIRT_MAC_OUI: &[u8] = &[0x02, 0xf1, 0x0f];
|
const VIRT_MAC_OUI: &[u8] = &[0x02, 0xf1, 0x0f];
|
||||||
|
|
||||||
|
macro_rules! progress {
|
||||||
|
($task:ident, $pct:literal, $($arg:tt)*) => {{
|
||||||
|
let mut pt = $task.write().await;
|
||||||
|
pt.status_text = format!($($arg)*);
|
||||||
|
pt.percentage = $pct;
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new instance
|
/// Creates a new instance
|
||||||
pub async fn new_instance(
|
pub async fn new_instance(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
|
prog_task: Arc<RwLock<Progress>>,
|
||||||
args: &args::NewInstance,
|
args: &args::NewInstance,
|
||||||
) -> Result<Instance, Box<dyn std::error::Error>> {
|
) -> Result<Instance, Box<dyn std::error::Error>> {
|
||||||
|
progress!(prog_task, 0.0, "Starting...");
|
||||||
// find the subnet corresponding to the interface
|
// find the subnet corresponding to the interface
|
||||||
let subnet = Subnet::get_by_key(ctx.db.clone(), args.interface.as_bytes())
|
let subnet = Subnet::get_by_key(ctx.db.clone(), args.subnet.as_bytes())
|
||||||
.map_err(|er| cmd_error!("Unable to get interface: {}", er))?
|
.map_err(|er| cmd_error!("Unable to get interface: {}", er))?
|
||||||
.map_or(
|
.map_or(
|
||||||
Err(cmd_error!(
|
Err(cmd_error!(
|
||||||
"Interface {} wasn't found in database",
|
"Subnet {} wasn't found in database",
|
||||||
&args.interface
|
&args.subnet
|
||||||
)),
|
)),
|
||||||
Ok,
|
Ok,
|
||||||
)?;
|
)?;
|
||||||
|
@ -43,6 +55,7 @@ pub async fn new_instance(
|
||||||
// make sure the base image exists
|
// make sure the base image exists
|
||||||
let mut base_image = VirtVolume::lookup_by_name(&ctx.virt.pools.baseimg, &args.base_image)
|
let mut base_image = VirtVolume::lookup_by_name(&ctx.virt.pools.baseimg, &args.base_image)
|
||||||
.map_err(|er| cmd_error!("Couldn't find base image: {}", er))?;
|
.map_err(|er| cmd_error!("Couldn't find base image: {}", er))?;
|
||||||
|
progress!(prog_task, 10.0, "Generating metadata...");
|
||||||
|
|
||||||
// generate a new lease with a new MAC addr
|
// generate a new lease with a new MAC addr
|
||||||
let mac_addr = {
|
let mac_addr = {
|
||||||
|
@ -99,6 +112,7 @@ pub async fn new_instance(
|
||||||
// mark the stream as finished
|
// mark the stream as finished
|
||||||
cistream.finish()?;
|
cistream.finish()?;
|
||||||
|
|
||||||
|
progress!(prog_task, 30.0, "Creating instance images...");
|
||||||
// create primary volume from base image
|
// create primary volume from base image
|
||||||
let mut pri_vol = base_image
|
let mut pri_vol = base_image
|
||||||
.clone_vol(
|
.clone_vol(
|
||||||
|
@ -126,6 +140,12 @@ pub async fn new_instance(
|
||||||
};
|
};
|
||||||
|
|
||||||
// build domain xml
|
// build domain xml
|
||||||
|
let ifname = subnet.ifname.clone();
|
||||||
|
let devname = format!(
|
||||||
|
"veth-{:02x}{:02x}{:02x}",
|
||||||
|
mac_addr[3], mac_addr[4], mac_addr[5]
|
||||||
|
);
|
||||||
|
progress!(prog_task, 60.0, "Initializing instance...");
|
||||||
let (mut inst, conn) = Instance::new(ctx.clone(), subnet, lease, {
|
let (mut inst, conn) = Instance::new(ctx.clone(), subnet, lease, {
|
||||||
let pri_name = &ctx.virt.pools.primary.xml.name;
|
let pri_name = &ctx.virt.pools.primary.xml.name;
|
||||||
let sec_name = &ctx.virt.pools.secondary.xml.name;
|
let sec_name = &ctx.virt.pools.secondary.xml.name;
|
||||||
|
@ -135,7 +155,11 @@ pub async fn new_instance(
|
||||||
.name(&args.name)
|
.name(&args.name)
|
||||||
.memory(datasize!((args.memory) MiB))
|
.memory(datasize!((args.memory) MiB))
|
||||||
.cpu_topology(1, 1, args.cores, 1)
|
.cpu_topology(1, 1, args.cores, 1)
|
||||||
.net_device(|nd| nd.mac_addr(&mac_addr).with_bridge(&args.interface))
|
.net_device(|nd| {
|
||||||
|
nd.mac_addr(&mac_addr)
|
||||||
|
.with_bridge(&ifname)
|
||||||
|
.target_dev(&devname)
|
||||||
|
})
|
||||||
.disk_device(|dsk| {
|
.disk_device(|dsk| {
|
||||||
dsk.volume_source(pri_name, &pri_vol.name)
|
dsk.volume_source(pri_name, &pri_vol.name)
|
||||||
.target("vda", "virtio")
|
.target("vda", "virtio")
|
||||||
|
@ -170,9 +194,12 @@ pub async fn new_instance(
|
||||||
warn!("Couldn't set autostart for domain: {}", er);
|
warn!("Couldn't set autostart for domain: {}", er);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
if let Err(er) = conn.create() {
|
if let Err(er) = conn.create() {
|
||||||
warn!("Domain defined, but couldn't be started! Error: {}", er);
|
warn!("Domain defined, but couldn't be started! Error: {}", er);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
// set all volumes to persistent to avoid deletion
|
// set all volumes to persistent to avoid deletion
|
||||||
pri_vol.persist = true;
|
pri_vol.persist = true;
|
||||||
|
@ -182,6 +209,7 @@ pub async fn new_instance(
|
||||||
cidata_vol.persist = true;
|
cidata_vol.persist = true;
|
||||||
inst.persist();
|
inst.persist();
|
||||||
|
|
||||||
|
progress!(prog_task, 80.0, "Domain created!");
|
||||||
debug!("Domain {} created!", inst.xml().name.as_str());
|
debug!("Domain {} created!", inst.xml().name.as_str());
|
||||||
Ok(inst)
|
Ok(inst)
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,11 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace(&mut self, other: T) -> Result<(), StorableError> {
|
||||||
|
self.inner = other;
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn delete(&self) -> Result<(), StorableError> {
|
pub fn delete(&self) -> Result<(), StorableError> {
|
||||||
self.on_delete(&self.db)?;
|
self.on_delete(&self.db)?;
|
||||||
self.tree
|
self.tree
|
||||||
|
|
|
@ -1,47 +1,45 @@
|
||||||
use super::{Entity, StorIter};
|
use super::{Entity, StorIter};
|
||||||
use nzr_api::model::IfaceStr;
|
use nzr_api::model::SubnetData;
|
||||||
use nzr_api::net::cidr::CidrV4;
|
use nzr_api::net::cidr::CidrV4;
|
||||||
use nzr_api::net::mac::MacAddr;
|
use nzr_api::net::mac::MacAddr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::net::Ipv4Addr;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use trust_dns_server::proto::rr::Name;
|
|
||||||
|
|
||||||
use super::Storable;
|
use super::Storable;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Subnet {
|
pub struct Subnet {
|
||||||
pub interface: String,
|
pub model: SubnetData,
|
||||||
pub network: CidrV4,
|
|
||||||
pub gateway4: Ipv4Addr,
|
|
||||||
pub dns: Vec<Ipv4Addr>,
|
|
||||||
pub domain_name: Option<Name>,
|
|
||||||
pub start_host: u32,
|
|
||||||
pub end_host: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Subnet> for nzr_api::model::Subnet {
|
impl Deref for Subnet {
|
||||||
|
type Target = SubnetData;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Subnet> for SubnetData {
|
||||||
fn from(value: &Subnet) -> Self {
|
fn from(value: &Subnet) -> Self {
|
||||||
let start_host = value.network.make_ip(value.start_host).unwrap();
|
value.model.clone()
|
||||||
let end_host = value.network.make_ip(value.end_host).unwrap();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SubnetData> for Subnet {
|
||||||
|
fn from(value: &SubnetData) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ifname: IfaceStr::from_str(&value.interface).unwrap(),
|
model: value.clone(),
|
||||||
data: nzr_api::model::SubnetData {
|
|
||||||
network: value.network.clone(),
|
|
||||||
start_host,
|
|
||||||
end_host,
|
|
||||||
gateway4: Some(value.gateway4),
|
|
||||||
dns: value.dns.clone(),
|
|
||||||
domain_name: value.domain_name.to_owned(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Lease {
|
pub struct Lease {
|
||||||
|
pub subnet: String,
|
||||||
pub ipv4_addr: CidrV4,
|
pub ipv4_addr: CidrV4,
|
||||||
pub mac_addr: MacAddr,
|
pub mac_addr: MacAddr,
|
||||||
pub inst_name: String,
|
pub inst_name: String,
|
||||||
|
@ -103,43 +101,17 @@ impl Storable for Subnet {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subnet {
|
impl Subnet {
|
||||||
pub fn new(
|
pub fn from_model(data: &nzr_api::model::SubnetData) -> Result<Self, SubnetError> {
|
||||||
iface: &str,
|
|
||||||
network: &CidrV4,
|
|
||||||
start_addr: &Ipv4Addr,
|
|
||||||
end_addr: &Ipv4Addr,
|
|
||||||
gateway4: Option<&Ipv4Addr>,
|
|
||||||
dns: &[Ipv4Addr],
|
|
||||||
domain_name: Option<Name>,
|
|
||||||
) -> Result<Self, SubnetError> {
|
|
||||||
// validate start and end addresses
|
// validate start and end addresses
|
||||||
if end_addr < start_addr {
|
if data.end_host < data.start_host {
|
||||||
Err(SubnetError::BadRange)
|
Err(SubnetError::BadRange)
|
||||||
} else if !network.contains(start_addr) {
|
} else if !data.network.contains(&data.start_host) {
|
||||||
Err(SubnetError::BadStartHost)
|
Err(SubnetError::BadStartHost)
|
||||||
} else if !network.contains(end_addr) {
|
} else if !data.network.contains(&data.end_host) {
|
||||||
Err(SubnetError::BadEndHost)
|
Err(SubnetError::BadEndHost)
|
||||||
} else {
|
} else {
|
||||||
let gateway4 = gateway4.cloned().unwrap_or(
|
|
||||||
network
|
|
||||||
.make_ip(1)
|
|
||||||
.map_err(|_| SubnetError::HostOutsideRange)?,
|
|
||||||
);
|
|
||||||
let mut dns = dns.to_owned();
|
|
||||||
if dns.is_empty() {
|
|
||||||
// default DNS: quad9
|
|
||||||
dns.push(Ipv4Addr::new(9, 9, 9, 9));
|
|
||||||
}
|
|
||||||
let start_host = network.host_bits(start_addr);
|
|
||||||
let end_host = network.host_bits(end_addr);
|
|
||||||
let subnet = Subnet {
|
let subnet = Subnet {
|
||||||
interface: iface.to_owned(),
|
model: data.clone(),
|
||||||
network: network.clone(),
|
|
||||||
start_host,
|
|
||||||
end_host,
|
|
||||||
gateway4,
|
|
||||||
dns,
|
|
||||||
domain_name,
|
|
||||||
};
|
};
|
||||||
Ok(subnet)
|
Ok(subnet)
|
||||||
}
|
}
|
||||||
|
@ -148,7 +120,7 @@ impl Subnet {
|
||||||
/// Gets the lease tree from sled.
|
/// Gets the lease tree from sled.
|
||||||
pub fn lease_tree(&self) -> Vec<u8> {
|
pub fn lease_tree(&self) -> Vec<u8> {
|
||||||
let mut lt_name: Vec<u8> = vec![b'L'];
|
let mut lt_name: Vec<u8> = vec![b'L'];
|
||||||
lt_name.extend_from_slice(&self.network.octets());
|
lt_name.extend_from_slice(&self.model.network.octets());
|
||||||
lt_name
|
lt_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,14 +141,16 @@ impl Entity<Subnet> {
|
||||||
let tree = self.db.open_tree(self.lease_tree())?;
|
let tree = self.db.open_tree(self.lease_tree())?;
|
||||||
let max_lease = match tree.last()? {
|
let max_lease = match tree.last()? {
|
||||||
Some(lease) => u32::from_be_bytes(lease.0[..4].try_into().unwrap()),
|
Some(lease) => u32::from_be_bytes(lease.0[..4].try_into().unwrap()),
|
||||||
None => self.start_host,
|
None => self.model.start_bytes(),
|
||||||
};
|
};
|
||||||
let new_ip = self
|
let new_ip = self
|
||||||
|
.model
|
||||||
.network
|
.network
|
||||||
.make_ip(max_lease + 1)
|
.make_ip(max_lease + 1)
|
||||||
.map_err(|_| SubnetError::SubnetFull)?;
|
.map_err(|_| SubnetError::SubnetFull)?;
|
||||||
let lease_data = Lease {
|
let lease_data = Lease {
|
||||||
ipv4_addr: CidrV4::new(new_ip, self.network.cidr()),
|
subnet: String::from_utf8_lossy(&self.key).to_string(),
|
||||||
|
ipv4_addr: CidrV4::new(new_ip, self.model.network.cidr()),
|
||||||
mac_addr: mac_addr.clone(),
|
mac_addr: mac_addr.clone(),
|
||||||
inst_name: inst_name.to_owned(),
|
inst_name: inst_name.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -179,6 +179,13 @@ impl IfaceBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn target_dev(mut self, name: &str) -> Self {
|
||||||
|
self.iface.target = Some(NetTarget {
|
||||||
|
dev: name.to_owned(),
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn build(self) -> NetDevice {
|
fn build(self) -> NetDevice {
|
||||||
self.iface
|
self.iface
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,6 +224,12 @@ impl Default for NetModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct NetTarget {
|
||||||
|
#[serde(rename = "@dev")]
|
||||||
|
dev: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum IfaceType {
|
pub enum IfaceType {
|
||||||
|
@ -240,6 +246,7 @@ pub struct NetDevice {
|
||||||
mac: Option<NetMac>,
|
mac: Option<NetMac>,
|
||||||
source: NetSource,
|
source: NetSource,
|
||||||
model: NetModel,
|
model: NetModel,
|
||||||
|
target: Option<NetTarget>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=
|
// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::ctrl::net::Lease;
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use log::*;
|
use log::*;
|
||||||
use nzr_api::net::cidr::CidrV4;
|
use nzr_api::net::cidr::CidrV4;
|
||||||
|
use nzr_api::net::mac::MacAddr;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::str::{self, Utf8Error};
|
use std::str::{self, Utf8Error};
|
||||||
|
|
||||||
|
@ -11,10 +12,16 @@ use super::{net::Subnet, Entity};
|
||||||
use crate::virt::*;
|
use crate::virt::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Progress {
|
||||||
|
pub status_text: String,
|
||||||
|
pub percentage: f32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct InstDb {
|
pub struct InstDb {
|
||||||
uuid: uuid::Uuid,
|
uuid: uuid::Uuid,
|
||||||
lease_if: String,
|
lease_subnet: Vec<u8>,
|
||||||
lease_addr: CidrV4,
|
lease_addr: CidrV4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +37,21 @@ impl Storable for InstDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Entity<InstDb>> for nzr_api::model::Instance {
|
||||||
|
fn from(value: Entity<InstDb>) -> Self {
|
||||||
|
nzr_api::model::Instance {
|
||||||
|
name: String::from_utf8_lossy(&value.key).to_string(),
|
||||||
|
uuid: value.uuid,
|
||||||
|
lease: Some(nzr_api::model::Lease {
|
||||||
|
subnet: String::from_utf8_lossy(&value.lease_subnet).to_string(),
|
||||||
|
addr: value.lease_addr.clone(),
|
||||||
|
mac_addr: MacAddr::invalid(),
|
||||||
|
}),
|
||||||
|
state: nzr_api::model::DomainState::NoState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
db_data: Entity<InstDb>,
|
db_data: Entity<InstDb>,
|
||||||
lease: Option<Entity<Lease>>,
|
lease: Option<Entity<Lease>>,
|
||||||
|
@ -77,12 +99,12 @@ impl Instance {
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Adding {} (interface: {}) to the instance tree...",
|
"Adding {} (interface: {}) to the instance tree...",
|
||||||
&lease.ipv4_addr, &subnet.interface,
|
&lease.ipv4_addr, &subnet.ifname,
|
||||||
);
|
);
|
||||||
|
|
||||||
let db_data = InstDb {
|
let db_data = InstDb {
|
||||||
uuid: real_xml.uuid,
|
uuid: real_xml.uuid,
|
||||||
lease_if: subnet.interface.clone(),
|
lease_subnet: subnet.key().to_vec(),
|
||||||
lease_addr: lease.ipv4_addr.clone(),
|
lease_addr: lease.ipv4_addr.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -148,6 +170,7 @@ impl Instance {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an Instance from a given InstDb entity.
|
||||||
pub fn from_entity(ctx: Context, db_data: Entity<InstDb>) -> Result<Self, InstanceError> {
|
pub fn from_entity(ctx: Context, db_data: Entity<InstDb>) -> Result<Self, InstanceError> {
|
||||||
let name = String::from_utf8_lossy(&db_data.key).into_owned();
|
let name = String::from_utf8_lossy(&db_data.key).into_owned();
|
||||||
let virt_domain = match virt::domain::Domain::lookup_by_name(&ctx.virt.conn, &name) {
|
let virt_domain = match virt::domain::Domain::lookup_by_name(&ctx.virt.conn, &name) {
|
||||||
|
@ -168,7 +191,7 @@ impl Instance {
|
||||||
quick_xml::de::from_str(&xml_str).map_err(InstanceError::CantDeserialize)?
|
quick_xml::de::from_str(&xml_str).map_err(InstanceError::CantDeserialize)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let lease = match Subnet::get_by_key(ctx.db.clone(), db_data.lease_if.as_bytes())
|
let lease = match Subnet::get_by_key(ctx.db.clone(), &db_data.lease_subnet)
|
||||||
.map_err(InstanceError::other)?
|
.map_err(InstanceError::other)?
|
||||||
{
|
{
|
||||||
Some(subnet) => subnet
|
Some(subnet) => subnet
|
||||||
|
@ -237,6 +260,7 @@ impl From<&Instance> for nzr_api::model::Instance {
|
||||||
name: value.domain_xml.name.clone(),
|
name: value.domain_xml.name.clone(),
|
||||||
uuid: value.domain_xml.uuid,
|
uuid: value.domain_xml.uuid,
|
||||||
lease: value.lease.as_ref().map(|l| nzr_api::model::Lease {
|
lease: value.lease.as_ref().map(|l| nzr_api::model::Lease {
|
||||||
|
subnet: l.subnet.clone(),
|
||||||
addr: l.ipv4_addr.clone(),
|
addr: l.ipv4_addr.clone(),
|
||||||
mac_addr: l.mac_addr.clone(),
|
mac_addr: l.mac_addr.clone(),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -126,13 +126,18 @@ impl InnerZD {
|
||||||
trust_dns_server::authority::ZoneType::Primary,
|
trust_dns_server::authority::ZoneType::Primary,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
self.import(&subnet.interface, auth).await;
|
self.import(&subnet.ifname.to_string(), auth).await;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn import(&self, name: &str, auth: InMemoryAuthority) {
|
pub async fn import(&self, name: &str, auth: InMemoryAuthority) {
|
||||||
let auth_arc = Arc::new(auth);
|
let auth_arc = Arc::new(auth);
|
||||||
|
log::debug!(
|
||||||
|
"Importing {} with {} records...",
|
||||||
|
name,
|
||||||
|
auth_arc.records().await.len()
|
||||||
|
);
|
||||||
self.map
|
self.map
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
|
@ -163,6 +168,12 @@ impl InnerZD {
|
||||||
hostname.append_domain(&origin)?
|
hostname.append_domain(&origin)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"Creating new host entry {} in zone {}...",
|
||||||
|
&fqdn,
|
||||||
|
zone.origin()
|
||||||
|
);
|
||||||
|
|
||||||
let record = Record::from_rdata(fqdn, 3600, RData::A(addr));
|
let record = Record::from_rdata(fqdn, 3600, RData::A(addr));
|
||||||
zone.upsert(record, 0).await;
|
zone.upsert(record, 0).await;
|
||||||
self.catalog()
|
self.catalog()
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::str::FromStr;
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
use trust_dns_server::ServerFuture;
|
use trust_dns_server::ServerFuture;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cfg: config::Config = config::Config::figment().extract()?;
|
let cfg: config::Config = config::Config::figment().extract()?;
|
||||||
let ctx = ctx::Context::new(cfg)?;
|
let ctx = ctx::Context::new(cfg)?;
|
||||||
|
@ -33,7 +33,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Ok(subnet) => {
|
Ok(subnet) => {
|
||||||
// A records
|
// A records
|
||||||
if let Err(err) = ctx.zones.new_zone(&subnet).await {
|
if let Err(err) = ctx.zones.new_zone(&subnet).await {
|
||||||
error!("Couldn't create zone for {}: {}", &subnet.interface, err);
|
error!("Couldn't create zone for {}: {}", &subnet.ifname, err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match subnet.leases() {
|
match subnet.leases() {
|
||||||
|
@ -44,7 +44,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if let Err(err) = ctx
|
if let Err(err) = ctx
|
||||||
.zones
|
.zones
|
||||||
.new_record(
|
.new_record(
|
||||||
&subnet.interface,
|
&subnet.ifname.to_string(),
|
||||||
&lease.inst_name,
|
&lease.inst_name,
|
||||||
lease.ipv4_addr.addr,
|
lease.ipv4_addr.addr,
|
||||||
)
|
)
|
||||||
|
@ -52,21 +52,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
{
|
{
|
||||||
error!(
|
error!(
|
||||||
"Failed to set up lease for {} in {}: {}",
|
"Failed to set up lease for {} in {}: {}",
|
||||||
&lease.inst_name, &subnet.interface, err
|
&lease.inst_name, &subnet.ifname, err
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(
|
warn!(
|
||||||
"Lease iterator error while hydrating {}: {}",
|
"Lease iterator error while hydrating {}: {}",
|
||||||
&subnet.interface, err
|
&subnet.ifname, err
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Couldn't get leases for {}: {}", &subnet.interface, err);
|
error!("Couldn't get leases for {}: {}", &subnet.ifname, err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
145
nzrd/src/rpc.rs
145
nzrd/src/rpc.rs
|
@ -1,8 +1,13 @@
|
||||||
use nzr_api::{args, model, Nazrin};
|
use nzr_api::{args, model, Nazrin};
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::sync::Arc;
|
||||||
use tarpc::server::{BaseChannel, Channel};
|
use tarpc::server::{BaseChannel, Channel};
|
||||||
use tarpc::tokio_serde::formats::Bincode;
|
use tarpc::tokio_serde::formats::Bincode;
|
||||||
use tarpc::tokio_util::codec::LengthDelimitedCodec;
|
use tarpc::tokio_util::codec::LengthDelimitedCodec;
|
||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::ctrl::vm::InstDb;
|
use crate::ctrl::vm::InstDb;
|
||||||
use crate::ctrl::{net::Subnet, Storable};
|
use crate::ctrl::{net::Subnet, Storable};
|
||||||
|
@ -10,17 +15,23 @@ use crate::ctx::Context;
|
||||||
use crate::dns::ZoneData;
|
use crate::dns::ZoneData;
|
||||||
use crate::{cmd, ctrl::vm::Instance};
|
use crate::{cmd, ctrl::vm::Instance};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NzrServer {
|
pub struct NzrServer {
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
zones: ZoneData,
|
zones: ZoneData,
|
||||||
|
create_tasks: Arc<RwLock<HashMap<Uuid, InstCreateStatus>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NzrServer {
|
impl NzrServer {
|
||||||
pub fn new(ctx: Context, zones: ZoneData) -> Self {
|
pub fn new(ctx: Context, zones: ZoneData) -> Self {
|
||||||
Self { ctx, zones }
|
Self {
|
||||||
|
ctx,
|
||||||
|
zones,
|
||||||
|
create_tasks: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,21 +41,79 @@ impl Nazrin for NzrServer {
|
||||||
self,
|
self,
|
||||||
_: tarpc::context::Context,
|
_: tarpc::context::Context,
|
||||||
build_args: args::NewInstance,
|
build_args: args::NewInstance,
|
||||||
) -> Result<model::Instance, String> {
|
) -> Result<uuid::Uuid, String> {
|
||||||
let inst = cmd::vm::new_instance(self.ctx.clone(), &build_args)
|
let progress = Arc::new(RwLock::new(crate::ctrl::vm::Progress {
|
||||||
|
status_text: "Starting...".to_owned(),
|
||||||
|
percentage: 0.0,
|
||||||
|
}));
|
||||||
|
let prog_task = progress.clone();
|
||||||
|
let build_task = tokio::spawn(async move {
|
||||||
|
let inst = cmd::vm::new_instance(self.ctx.clone(), prog_task.clone(), &build_args)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Instance creation failed: {}", e))?;
|
.map_err(|e| format!("Instance creation failed: {}", e))?;
|
||||||
let addr = inst.ip_lease().map(|l| l.ipv4_addr.addr);
|
let addr = inst.ip_lease().map(|l| l.ipv4_addr.addr);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut pt = prog_task.write().await;
|
||||||
|
pt.status_text = "Starting instance...".to_owned();
|
||||||
|
pt.percentage = 90.0;
|
||||||
|
}
|
||||||
if let Some(addr) = addr {
|
if let Some(addr) = addr {
|
||||||
if let Err(err) = self
|
if let Err(err) = self
|
||||||
.zones
|
.zones
|
||||||
.new_record(&build_args.interface, &build_args.name, addr)
|
.new_record(&build_args.subnet, &build_args.name, addr)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
warn!("Instance created, but no DNS record was made: {}", err);
|
warn!("Instance created, but no DNS record was made: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((&inst).into())
|
Ok((&inst).into())
|
||||||
|
});
|
||||||
|
|
||||||
|
let task_id = uuid::Uuid::new_v4();
|
||||||
|
self.create_tasks.write().await.insert(
|
||||||
|
task_id,
|
||||||
|
InstCreateStatus {
|
||||||
|
inner: build_task,
|
||||||
|
progress,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(task_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn poll_new_instance(
|
||||||
|
self,
|
||||||
|
_: tarpc::context::Context,
|
||||||
|
task_id: uuid::Uuid,
|
||||||
|
) -> Option<model::CreateStatus> {
|
||||||
|
let (progress, is_finished) = {
|
||||||
|
match self.create_tasks.read().await.get(&task_id) {
|
||||||
|
Some(st) => (st.progress.read().await.clone(), st.inner.is_finished()),
|
||||||
|
None => {
|
||||||
|
debug!("Task ID {} not found", task_id);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = if is_finished {
|
||||||
|
let task = self.create_tasks.write().await.remove(&task_id).unwrap();
|
||||||
|
Some(
|
||||||
|
task.inner
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Task failed with panic: {}", err))
|
||||||
|
.and_then(|res| res),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(model::CreateStatus {
|
||||||
|
status_text: progress.status_text,
|
||||||
|
completion: progress.percentage,
|
||||||
|
result,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_instance(self, _: tarpc::context::Context, name: String) -> Result<(), String> {
|
async fn delete_instance(self, _: tarpc::context::Context, name: String) -> Result<(), String> {
|
||||||
|
@ -57,12 +126,17 @@ impl Nazrin for NzrServer {
|
||||||
async fn get_instances(
|
async fn get_instances(
|
||||||
self,
|
self,
|
||||||
_: tarpc::context::Context,
|
_: tarpc::context::Context,
|
||||||
|
with_status: bool,
|
||||||
) -> Result<Vec<model::Instance>, String> {
|
) -> Result<Vec<model::Instance>, String> {
|
||||||
let insts: Vec<model::Instance> = InstDb::all(self.ctx.db.clone())
|
let insts: Vec<model::Instance> = InstDb::all(self.ctx.db.clone())
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.filter_map(|i| match i {
|
.filter_map(|i| match i {
|
||||||
Ok(entity) => match Instance::from_entity(self.ctx.clone(), entity.clone()) {
|
Ok(entity) => {
|
||||||
Ok(instance) => Some(<&Instance as Into<model::Instance>>::into(&instance)),
|
if with_status {
|
||||||
|
match Instance::from_entity(self.ctx.clone(), entity.clone()) {
|
||||||
|
Ok(instance) => {
|
||||||
|
Some(<&Instance as Into<model::Instance>>::into(&instance))
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let ent_name = {
|
let ent_name = {
|
||||||
let key = entity.key();
|
let key = entity.key();
|
||||||
|
@ -71,7 +145,11 @@ impl Nazrin for NzrServer {
|
||||||
warn!("Couldn't get instance for {}: {}", err, ent_name);
|
warn!("Couldn't get instance for {}: {}", err, ent_name);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
} else {
|
||||||
|
Some(entity.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("Iterator error: {}", err);
|
warn!("Iterator error: {}", err);
|
||||||
None
|
None
|
||||||
|
@ -93,14 +171,51 @@ impl Nazrin for NzrServer {
|
||||||
.new_zone(&subnet)
|
.new_zone(&subnet)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(<&Subnet as Into<model::Subnet>>::into(&subnet))
|
Ok(model::Subnet {
|
||||||
|
name: String::from_utf8_lossy(subnet.key()).to_string(),
|
||||||
|
data: <&Subnet as Into<model::SubnetData>>::into(&subnet),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn modify_subnet(
|
||||||
|
self,
|
||||||
|
_: tarpc::context::Context,
|
||||||
|
edit_args: model::Subnet,
|
||||||
|
) -> Result<model::Subnet, String> {
|
||||||
|
let subnet = Subnet::all(self.ctx.db.clone())
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.find_map(|sub| {
|
||||||
|
if let Ok(sub) = sub {
|
||||||
|
if edit_args.name.as_str() == String::from_utf8_lossy(sub.key()) {
|
||||||
|
Some(sub)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(mut subnet) = subnet {
|
||||||
|
subnet
|
||||||
|
.replace(edit_args.data.borrow().into())
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(model::Subnet {
|
||||||
|
name: edit_args.name,
|
||||||
|
data: subnet.deref().into(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(format!("Subnet {} not found", &edit_args.name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_subnets(self, _: tarpc::context::Context) -> Result<Vec<model::Subnet>, String> {
|
async fn get_subnets(self, _: tarpc::context::Context) -> Result<Vec<model::Subnet>, String> {
|
||||||
let subnets: Vec<model::Subnet> = Subnet::all(self.ctx.db.clone())
|
let subnets: Vec<model::Subnet> = Subnet::all(self.ctx.db.clone())
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.filter_map(|s| match s {
|
.filter_map(|s| match s {
|
||||||
Ok(s) => Some(<&Subnet as Into<model::Subnet>>::into(s.deref())),
|
Ok(s) => Some(model::Subnet {
|
||||||
|
name: String::from_utf8(s.key().to_vec()).unwrap(),
|
||||||
|
data: <&Subnet as Into<model::SubnetData>>::into(s.deref()),
|
||||||
|
}),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("Iterator error: {}", err);
|
warn!("Iterator error: {}", err);
|
||||||
None
|
None
|
||||||
|
@ -157,11 +272,21 @@ pub async fn serve(ctx: Context, zones: ZoneData) -> Result<(), Box<dyn std::err
|
||||||
|
|
||||||
let codec_builder = LengthDelimitedCodec::builder();
|
let codec_builder = LengthDelimitedCodec::builder();
|
||||||
loop {
|
loop {
|
||||||
|
debug!("Listening for new connection...");
|
||||||
let (conn, _addr) = listener.accept().await?;
|
let (conn, _addr) = listener.accept().await?;
|
||||||
|
let (ctx, zones) = (ctx.clone(), zones.clone());
|
||||||
|
// hack?
|
||||||
|
tokio::spawn(async move {
|
||||||
let framed = codec_builder.new_framed(conn);
|
let framed = codec_builder.new_framed(conn);
|
||||||
let transport = tarpc::serde_transport::new(framed, Bincode::default());
|
let transport = tarpc::serde_transport::new(framed, Bincode::default());
|
||||||
BaseChannel::with_defaults(transport)
|
BaseChannel::with_defaults(transport)
|
||||||
.execute(NzrServer::new(ctx.clone(), zones.clone()).serve())
|
.execute(NzrServer::new(ctx, zones).serve())
|
||||||
.await;
|
.await;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct InstCreateStatus {
|
||||||
|
inner: JoinHandle<Result<model::Instance, String>>,
|
||||||
|
progress: Arc<RwLock<crate::ctrl::vm::Progress>>,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue