nzrd: don't store ci-metadata
This will be handled entirely in omyacid.
This commit is contained in:
		
							parent
							
								
									3d0ea1f2ef
								
							
						
					
					
						commit
						da51722c54
					
				
					 11 changed files with 118 additions and 223 deletions
				
			
		|  | @ -1,3 +1,5 @@ | |||
| use std::net::Ipv4Addr; | ||||
| 
 | ||||
| use model::{CreateStatus, Instance, Subnet}; | ||||
| 
 | ||||
| pub mod args; | ||||
|  | @ -6,6 +8,15 @@ pub mod model; | |||
| pub mod net; | ||||
| 
 | ||||
| pub use hickory_proto; | ||||
| use net::mac::MacAddr; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub enum InstanceQuery { | ||||
|     Name(String), | ||||
|     MacAddr(MacAddr), | ||||
|     Ipv4Addr(Ipv4Addr), | ||||
| } | ||||
| 
 | ||||
| #[tarpc::service] | ||||
| pub trait Nazrin { | ||||
|  | @ -18,6 +29,8 @@ pub trait Nazrin { | |||
|     /// This should involve deleting all related disks and clearing
 | ||||
|     /// the lease information from the subnet data, if any.
 | ||||
|     async fn delete_instance(name: String) -> Result<(), String>; | ||||
|     /// Gets a single instance by the given InstanceQuery.
 | ||||
|     async fn find_instance(query: InstanceQuery) -> Result<Option<Instance>, String>; | ||||
|     /// Gets a list of existing instances.
 | ||||
|     async fn get_instances(with_status: bool) -> Result<Vec<Instance>, String>; | ||||
|     /// Cleans up unusable entries in the database.
 | ||||
|  | @ -34,4 +47,20 @@ pub trait Nazrin { | |||
|     async fn get_subnets() -> Result<Vec<Subnet>, String>; | ||||
|     /// Deletes an existing subnet.
 | ||||
|     async fn delete_subnet(interface: String) -> Result<(), String>; | ||||
|     // Gets the cloud-init user-data for the given instance.
 | ||||
|     async fn get_instance_userdata(id: i32) -> Result<Vec<u8>, String>; | ||||
| } | ||||
| 
 | ||||
| /// Create a new NazrinClient.
 | ||||
| pub fn new_client(sock: tokio::net::UnixStream) -> NazrinClient { | ||||
|     use tarpc::tokio_serde::formats::Bincode; | ||||
|     use tarpc::tokio_util::codec::LengthDelimitedCodec; | ||||
| 
 | ||||
|     let framed_io = LengthDelimitedCodec::builder() | ||||
|         .length_field_type::<u32>() | ||||
|         .new_framed(sock); | ||||
|     let transport = tarpc::serde_transport::new(framed_io, Bincode::default()); | ||||
|     NazrinClient::new(Default::default(), transport).spawn() | ||||
| } | ||||
| 
 | ||||
| pub use tarpc::context::current as default_ctx; | ||||
|  |  | |||
							
								
								
									
										1
									
								
								nzrd/migrations/2024081101_no_cimeta/down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								nzrd/migrations/2024081101_no_cimeta/down.sql
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| ALTER TABLE instances ADD COLUMN ci_metadata TEXT NOT NULL; | ||||
							
								
								
									
										1
									
								
								nzrd/migrations/2024081101_no_cimeta/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								nzrd/migrations/2024081101_no_cimeta/up.sql
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| ALTER TABLE instances DROP COLUMN ci_metadata; | ||||
|  | @ -1,149 +0,0 @@ | |||
| use std::net::Ipv4Addr; | ||||
| 
 | ||||
| use hickory_server::proto::rr::Name; | ||||
| use serde::Serialize; | ||||
| use serde_with::skip_serializing_none; | ||||
| use std::collections::HashMap; | ||||
| 
 | ||||
| use nzr_api::net::{cidr::CidrV4, mac::MacAddr}; | ||||
| 
 | ||||
| #[derive(Debug, Serialize)] | ||||
| #[serde(rename_all = "kebab-case")] | ||||
| pub struct Metadata<'a> { | ||||
|     instance_id: &'a str, | ||||
|     local_hostname: &'a str, | ||||
|     public_keys: Option<Vec<&'a String>>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> Metadata<'a> { | ||||
|     pub fn new(instance_id: &'a str) -> Self { | ||||
|         Self { | ||||
|             instance_id, | ||||
|             local_hostname: instance_id, | ||||
|             public_keys: None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn ssh_pubkeys(mut self, pubkeys: &'a [String]) -> Self { | ||||
|         self.public_keys = Some(pubkeys.iter().filter(|i| !i.is_empty()).collect()); | ||||
|         self | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize)] | ||||
| pub struct NetworkMeta<'a> { | ||||
|     version: u32, | ||||
|     ethernets: HashMap<String, EtherNic<'a>>, | ||||
|     #[serde(skip)] | ||||
|     ethnum: u8, | ||||
| } | ||||
| 
 | ||||
| impl<'a> NetworkMeta<'a> { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             version: 2, | ||||
|             ethernets: HashMap::new(), | ||||
|             ethnum: 0, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Define a NIC with a static address.
 | ||||
|     pub fn static_nic( | ||||
|         mut self, | ||||
|         match_data: EtherMatch<'a>, | ||||
|         cidr: &'a CidrV4, | ||||
|         gateway: &'a Ipv4Addr, | ||||
|         dns: DNSMeta<'a>, | ||||
|     ) -> Self { | ||||
|         self.ethernets.insert( | ||||
|             format!("eth{}", self.ethnum), | ||||
|             EtherNic { | ||||
|                 r#match: match_data, | ||||
|                 addresses: Some(vec![cidr]), | ||||
|                 gateway4: Some(gateway), | ||||
|                 dhcp4: false, | ||||
|                 nameservers: Some(dns), | ||||
|             }, | ||||
|         ); | ||||
|         self.ethnum += 1; | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn dhcp_nic(mut self, match_data: EtherMatch<'a>) -> Self { | ||||
|         self.ethernets.insert( | ||||
|             format!("eth{}", self.ethnum), | ||||
|             EtherNic { | ||||
|                 r#match: match_data, | ||||
|                 addresses: None, | ||||
|                 gateway4: None, | ||||
|                 dhcp4: true, | ||||
|                 nameservers: None, | ||||
|             }, | ||||
|         ); | ||||
|         self.ethnum += 1; | ||||
|         self | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize)] | ||||
| pub struct Ethernets<'a> { | ||||
|     nics: Vec<EtherNic<'a>>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize)] | ||||
| pub struct EtherNic<'a> { | ||||
|     r#match: EtherMatch<'a>, | ||||
|     addresses: Option<Vec<&'a CidrV4>>, | ||||
|     gateway4: Option<&'a Ipv4Addr>, | ||||
|     dhcp4: bool, | ||||
|     nameservers: Option<DNSMeta<'a>>, | ||||
| } | ||||
| 
 | ||||
| #[skip_serializing_none] | ||||
| #[derive(Default, Debug, Serialize)] | ||||
| pub struct EtherMatch<'a> { | ||||
|     name: Option<&'a str>, | ||||
|     macaddress: Option<&'a MacAddr>, | ||||
|     driver: Option<&'a str>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> EtherMatch<'a> { | ||||
|     #[allow(dead_code)] | ||||
|     pub fn name(name: &'a str) -> Self { | ||||
|         Self { | ||||
|             name: Some(name), | ||||
|             ..Default::default() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn mac_addr(addr: &'a MacAddr) -> Self { | ||||
|         Self { | ||||
|             macaddress: Some(addr), | ||||
|             ..Default::default() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[allow(dead_code)] | ||||
|     pub fn driver(driver: &'a str) -> Self { | ||||
|         Self { | ||||
|             driver: Some(driver), | ||||
|             ..Default::default() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize)] | ||||
| pub struct DNSMeta<'a> { | ||||
|     search: Vec<Name>, | ||||
|     addresses: &'a Vec<Ipv4Addr>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> DNSMeta<'a> { | ||||
|     pub fn with_addrs(search: Option<Vec<Name>>, addrs: &'a Vec<Ipv4Addr>) -> Self { | ||||
|         Self { | ||||
|             addresses: addrs, | ||||
|             search: search.unwrap_or_default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -6,7 +6,6 @@ use nzr_virt::{datasize, dom, vol}; | |||
| use tokio::sync::RwLock; | ||||
| 
 | ||||
| use super::*; | ||||
| use crate::cloud::Metadata; | ||||
| use crate::ctrl::vm::Progress; | ||||
| use crate::ctx::Context; | ||||
| use crate::model::{Instance, Subnet}; | ||||
|  | @ -86,14 +85,7 @@ pub async fn new_instance( | |||
|         }; | ||||
| 
 | ||||
|         // generate cloud-init data
 | ||||
|         let ci_meta = { | ||||
|             let m = Metadata::new(&args.name).ssh_pubkeys(&args.ssh_keys); | ||||
|             serde_yaml::to_string(&m) | ||||
|                 .map_err(|err| cmd_error!("Couldn't generate cloud-init metadata: {err}")) | ||||
|         }?; | ||||
| 
 | ||||
|         let db_inst = | ||||
|             Instance::insert(&ctx, &args.name, &subnet, lease.clone(), ci_meta, None).await?; | ||||
|         let db_inst = Instance::insert(&ctx, &args.name, &subnet, lease.clone(), None).await?; | ||||
| 
 | ||||
|         progress!(prog_task, 30.0, "Creating instance images..."); | ||||
|         // create primary volume from base image
 | ||||
|  |  | |||
|  | @ -12,6 +12,10 @@ use crate::dns::ZoneData; | |||
| use nzr_api::config::Config; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| pub(crate) const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); | ||||
| 
 | ||||
| #[cfg(not(test))] | ||||
| const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); | ||||
| 
 | ||||
| pub struct PoolRefs { | ||||
|  |  | |||
|  | @ -1,19 +1,16 @@ | |||
| mod cloud; | ||||
| mod cmd; | ||||
| mod ctrl; | ||||
| mod ctx; | ||||
| mod dns; | ||||
| mod model; | ||||
| mod rpc; | ||||
| #[cfg(test)] | ||||
| mod test; | ||||
| 
 | ||||
| use hickory_server::ServerFuture; | ||||
| use log::LevelFilter; | ||||
| use log::*; | ||||
| use model::{Instance, Subnet}; | ||||
| use nzr_api::config; | ||||
| use std::str::FromStr; | ||||
| use std::{net::IpAddr, str::FromStr}; | ||||
| use tokio::net::UdpSocket; | ||||
| 
 | ||||
| #[tokio::main(flavor = "multi_thread")] | ||||
|  | @ -62,7 +59,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | |||
| 
 | ||||
|     // DNS init
 | ||||
|     let mut dns_listener = ServerFuture::new(ctx.zones.catalog()); | ||||
|     let dns_socket = UdpSocket::bind(ctx.config.dns.listen_addr.as_str()).await?; | ||||
|     let dns_socket = { | ||||
|         let dns_ip: IpAddr = ctx.config.dns.listen_addr.parse()?; | ||||
|         UdpSocket::bind((dns_ip, ctx.config.dns.port)).await? | ||||
|     }; | ||||
|     dns_listener.register_socket(dns_socket); | ||||
| 
 | ||||
|     tokio::select! { | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| use std::{net::Ipv4Addr, str::FromStr}; | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod test; | ||||
| pub mod tx; | ||||
| 
 | ||||
| use diesel::{associations::HasTable, prelude::*}; | ||||
|  | @ -33,7 +35,6 @@ diesel::table! { | |||
|         mac_addr -> Text, | ||||
|         subnet_id -> Integer, | ||||
|         host_num -> Integer, | ||||
|         ci_metadata -> Text, | ||||
|         ci_userdata -> Nullable<Binary>, | ||||
|     } | ||||
| } | ||||
|  | @ -71,7 +72,6 @@ pub struct Instance { | |||
|     pub mac_addr: MacAddr, | ||||
|     pub subnet_id: i32, | ||||
|     pub host_num: i32, | ||||
|     pub ci_metadata: String, | ||||
|     pub ci_userdata: Option<Vec<u8>>, | ||||
| } | ||||
| 
 | ||||
|  | @ -91,6 +91,17 @@ impl Instance { | |||
|         Ok(res) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn get(ctx: &Context, id: i32) -> Result<Option<Self>, ModelError> { | ||||
|         ctx.spawn_db(move |mut db| { | ||||
|             self::instances::table | ||||
|                 .find(id) | ||||
|                 .load::<Instance>(&mut db) | ||||
|                 .map(|m| m.into_iter().next()) | ||||
|         }) | ||||
|         .await? | ||||
|         .map_err(ModelError::Db) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn all_in_subnet(ctx: &Context, net: &Subnet) -> Result<Vec<Self>, ModelError> { | ||||
|         let subnet = net.clone(); | ||||
| 
 | ||||
|  | @ -122,6 +133,19 @@ impl Instance { | |||
|         Ok(res.into_iter().next()) | ||||
|     } | ||||
| 
 | ||||
|     /// Gets an Instance model with the given MAC address.
 | ||||
|     pub async fn get_by_mac(ctx: &Context, addr: MacAddr) -> Result<Option<Self>, ModelError> { | ||||
|         ctx.spawn_db(move |mut db| { | ||||
|             use self::instances::dsl::{instances, mac_addr}; | ||||
|             instances | ||||
|                 .filter(mac_addr.eq(addr)) | ||||
|                 .select(Instance::as_select()) | ||||
|                 .load::<Instance>(&mut db) | ||||
|         }) | ||||
|         .await? | ||||
|         .map_or_else(|e| Err(ModelError::Db(e)), |m| Ok(m.into_iter().next())) | ||||
|     } | ||||
| 
 | ||||
|     /// Gets an Instance model by the IPv4 address that has been assigned to it.
 | ||||
|     pub async fn get_by_ip4(ctx: &Context, ip_addr: Ipv4Addr) -> Result<Option<Self>, ModelError> { | ||||
|         use self::instances::dsl::host_num; | ||||
|  | @ -157,7 +181,6 @@ impl Instance { | |||
|         name: impl AsRef<str>, | ||||
|         subnet: &Subnet, | ||||
|         lease: nzr_api::model::Lease, | ||||
|         ci_meta: impl Into<String>, | ||||
|         ci_user: Option<Vec<u8>>, | ||||
|     ) -> Result<Self, ModelError> { | ||||
|         // Get highest host addr + 1 for our addr
 | ||||
|  | @ -169,7 +192,6 @@ impl Instance { | |||
| 
 | ||||
|         let wanted_name = name.as_ref().to_owned(); | ||||
|         let netid = subnet.id; | ||||
|         let ci_meta = ci_meta.into(); | ||||
| 
 | ||||
|         if addr_num > subnet.end_host { | ||||
|             Err(cidr::Error::HostBitsTooLarge)?; | ||||
|  | @ -184,7 +206,6 @@ impl Instance { | |||
|                     mac_addr.eq(lease.mac_addr), | ||||
|                     subnet_id.eq(netid), | ||||
|                     host_num.eq(addr_num), | ||||
|                     ci_metadata.eq(ci_meta), | ||||
|                     ci_userdata.eq(ci_user), | ||||
|                 ); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										14
									
								
								nzrd/src/model/test.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								nzrd/src/model/test.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| use diesel::Connection; | ||||
| use diesel_migrations::MigrationHarness; | ||||
| 
 | ||||
| #[test] | ||||
| fn migrations() { | ||||
|     let mut sql = diesel::SqliteConnection::establish(":memory:").unwrap(); | ||||
|     let pending = sql.pending_migrations(crate::ctx::MIGRATIONS).unwrap(); | ||||
|     assert!(!pending.is_empty(), "No migrations found"); | ||||
|     for migration in pending { | ||||
|         sql.run_migration(&migration).unwrap(); | ||||
|     } | ||||
| 
 | ||||
|     sql.revert_all_migrations(crate::ctx::MIGRATIONS).unwrap(); | ||||
| } | ||||
|  | @ -1,5 +1,5 @@ | |||
| use futures::{future, StreamExt}; | ||||
| use nzr_api::{args, model, Nazrin}; | ||||
| use nzr_api::{args, model, InstanceQuery, Nazrin}; | ||||
| use std::sync::Arc; | ||||
| use tarpc::server::{BaseChannel, Channel}; | ||||
| use tarpc::tokio_serde::formats::Bincode; | ||||
|  | @ -114,6 +114,27 @@ impl Nazrin for NzrServer { | |||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn find_instance( | ||||
|         self, | ||||
|         _: tarpc::context::Context, | ||||
|         query: nzr_api::InstanceQuery, | ||||
|     ) -> Result<Option<model::Instance>, String> { | ||||
|         let res = match query { | ||||
|             InstanceQuery::Name(name) => Instance::get_by_name(&self.ctx, name).await, | ||||
|             InstanceQuery::MacAddr(addr) => Instance::get_by_mac(&self.ctx, addr).await, | ||||
|             InstanceQuery::Ipv4Addr(addr) => Instance::get_by_ip4(&self.ctx, addr).await, | ||||
|         } | ||||
|         .map_err(|e| e.to_string())?; | ||||
| 
 | ||||
|         if let Some(inst) = res { | ||||
|             inst.api_model(&self.ctx) | ||||
|                 .await | ||||
|                 .map_or_else(|e| Err(e.to_string()), |m| Ok(Some(m))) | ||||
|         } else { | ||||
|             Ok(None) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async fn get_instances( | ||||
|         self, | ||||
|         _: tarpc::context::Context, | ||||
|  | @ -216,6 +237,21 @@ impl Nazrin for NzrServer { | |||
|             .map_err(|e| e.to_string())?; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn get_instance_userdata( | ||||
|         self, | ||||
|         _: tarpc::context::Context, | ||||
|         id: i32, | ||||
|     ) -> Result<Vec<u8>, String> { | ||||
|         let Some(db_model) = Instance::get(&self.ctx, id) | ||||
|             .await | ||||
|             .map_err(|e| e.to_string())? | ||||
|         else { | ||||
|             return Err("Instance doesn't exist".to_owned()); | ||||
|         }; | ||||
| 
 | ||||
|         Ok(db_model.ci_userdata.unwrap_or_default()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
|  |  | |||
|  | @ -1,54 +0,0 @@ | |||
| use std::{net::Ipv4Addr, str::FromStr}; | ||||
| 
 | ||||
| use crate::cloud::*; | ||||
| use nzr_api::net::{cidr::CidrV4, mac::MacAddr}; | ||||
| 
 | ||||
| #[test] | ||||
| fn cloud_metadata() { | ||||
|     let expected = r#" | ||||
| instance-id: my-instance | ||||
| local-hostname: my-instance | ||||
| public-keys: | ||||
| - ssh-key 123456 admin@laptop | ||||
| "#
 | ||||
|     .trim_start(); | ||||
|     let pubkeys = vec!["ssh-key 123456 admin@laptop".to_owned(), "".to_owned()]; | ||||
|     let meta = Metadata::new("my-instance").ssh_pubkeys(&pubkeys); | ||||
| 
 | ||||
|     let meta_xml = serde_yaml::to_string(&meta).unwrap(); | ||||
|     assert_eq!(meta_xml, expected); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn cloud_netdata() { | ||||
|     let expected = r#" | ||||
| version: 2 | ||||
| ethernets: | ||||
|   eth0: | ||||
|     match: | ||||
|       macaddress: 02:15:42:0b:ee:01 | ||||
|     addresses: | ||||
|     - 192.0.2.69/24 | ||||
|     gateway4: 192.0.2.1 | ||||
|     dhcp4: false | ||||
|     nameservers: | ||||
|       search: [] | ||||
|       addresses: | ||||
|       - 192.0.2.1 | ||||
| "#
 | ||||
|     .trim_start(); | ||||
|     let mac_addr = MacAddr::new(0x02, 0x15, 0x42, 0x0b, 0xee, 0x01); | ||||
|     let cidr = CidrV4::from_str("192.0.2.69/24").unwrap(); | ||||
|     let gateway = Ipv4Addr::from_str("192.0.2.1").unwrap(); | ||||
| 
 | ||||
|     let dns = vec![gateway]; | ||||
|     let netconfig = NetworkMeta::new().static_nic( | ||||
|         EtherMatch::mac_addr(&mac_addr), | ||||
|         &cidr, | ||||
|         &gateway, | ||||
|         DNSMeta::with_addrs(None, &dns), | ||||
|     ); | ||||
| 
 | ||||
|     let net_xml = serde_yaml::to_string(&netconfig).unwrap(); | ||||
|     assert_eq!(net_xml, expected); | ||||
| } | ||||
		Loading…
	
		Reference in a new issue