Compare commits
5 commits
6da77159b1
...
e4df2e5075
Author | SHA1 | Date | |
---|---|---|---|
snow flurry | e4df2e5075 | ||
snow flurry | 51e72fed93 | ||
snow flurry | da51722c54 | ||
snow flurry | 3d0ea1f2ef | ||
snow flurry | 9ca1b0c821 |
347
Cargo.lock
generated
347
Cargo.lock
generated
|
@ -108,6 +108,50 @@ version = "1.0.86"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "askama"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
|
||||
dependencies = [
|
||||
"askama_derive",
|
||||
"askama_escape",
|
||||
"humansize",
|
||||
"num-traits",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_derive"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
|
||||
dependencies = [
|
||||
"askama_parser",
|
||||
"basic-toml",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.72",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_escape"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
||||
|
||||
[[package]]
|
||||
name = "askama_parser"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.0"
|
||||
|
@ -121,13 +165,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.60"
|
||||
version = "0.1.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
|
||||
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.106",
|
||||
"syn 2.0.72",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -154,6 +198,61 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.73"
|
||||
|
@ -187,6 +286,15 @@ version = "1.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
|
@ -548,6 +656,16 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dhcproto"
|
||||
version = "0.12.0"
|
||||
|
@ -1145,12 +1263,101 @@ dependencies = [
|
|||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.53"
|
||||
|
@ -1366,6 +1573,12 @@ version = "0.1.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
|
@ -1403,6 +1616,22 @@ dependencies = [
|
|||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
|
@ -1514,6 +1743,12 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
|
@ -1646,7 +1881,6 @@ dependencies = [
|
|||
"moka",
|
||||
"nzr-api",
|
||||
"serde",
|
||||
"tarpc",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
@ -1661,6 +1895,20 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "omyacid"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
"axum",
|
||||
"moka",
|
||||
"nzr-api",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
|
@ -1865,6 +2113,12 @@ version = "0.3.26"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
|
@ -2098,6 +2352,12 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
|
@ -2162,6 +2422,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.7"
|
||||
|
@ -2285,9 +2555,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -2591,6 +2861,18 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||
|
||||
[[package]]
|
||||
name = "syslog"
|
||||
version = "7.0.0"
|
||||
|
@ -2720,13 +3002,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.17"
|
||||
version = "0.3.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
|
@ -2734,16 +3019,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.6"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
|
||||
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
|
@ -2877,6 +3163,34 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
|
@ -3005,6 +3319,15 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[workspace]
|
||||
members = ["nzrd", "nzr-api", "client", "nzrdhcp", "nzr-virt"]
|
||||
members = ["nzrd", "nzr-api", "client", "nzrdhcp", "nzr-virt", "omyacid"]
|
||||
resolver = "2"
|
||||
|
|
|
@ -32,6 +32,7 @@ pub struct SOAConfig {
|
|||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DNSConfig {
|
||||
pub listen_addr: String,
|
||||
pub port: u16,
|
||||
pub default_zone: Name,
|
||||
pub soa: SOAConfig,
|
||||
}
|
||||
|
@ -40,6 +41,15 @@ pub struct DNSConfig {
|
|||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DHCPConfig {
|
||||
pub listen_addr: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
/// Cloud-init configuration, used by omyacid.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CloudConfig {
|
||||
pub listen_addr: String,
|
||||
pub port: u16,
|
||||
pub admin_user: String,
|
||||
}
|
||||
|
||||
/// Server<->Client RPC configuration.
|
||||
|
@ -62,6 +72,7 @@ pub struct Config {
|
|||
pub storage: StorageConfig,
|
||||
pub dns: DNSConfig,
|
||||
pub dhcp: DHCPConfig,
|
||||
pub cloud: CloudConfig,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -84,8 +95,9 @@ impl Default for Config {
|
|||
base_image_pool: "images".to_owned(),
|
||||
},
|
||||
dns: DNSConfig {
|
||||
listen_addr: "127.0.0.1:5353".to_owned(),
|
||||
default_zone: Name::from_utf8("servers.locaddral").unwrap(),
|
||||
listen_addr: "127.0.0.1".to_owned(),
|
||||
port: 5353,
|
||||
default_zone: Name::from_utf8("servers.local").unwrap(),
|
||||
soa: SOAConfig {
|
||||
nzr_domain: Name::from_utf8("nzr.local").unwrap(),
|
||||
contact: Name::from_utf8("admin.nzr.local").unwrap(),
|
||||
|
@ -96,6 +108,12 @@ impl Default for Config {
|
|||
},
|
||||
dhcp: DHCPConfig {
|
||||
listen_addr: "127.0.0.1".to_owned(),
|
||||
port: 67,
|
||||
},
|
||||
cloud: CloudConfig {
|
||||
listen_addr: "0.0.0.0".to_owned(),
|
||||
port: 80,
|
||||
admin_user: "admin".to_owned(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
2
nzrd/migrations/2024080901_init/down.sql
Normal file
2
nzrd/migrations/2024080901_init/down.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE instances;
|
||||
DROP TABLE subnets;
|
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
|
||||
|
|
|
@ -3,6 +3,7 @@ use diesel::{
|
|||
SqliteConnection,
|
||||
};
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
use log::trace;
|
||||
use nzr_virt::{vol, Connection};
|
||||
use std::ops::Deref;
|
||||
use thiserror::Error;
|
||||
|
@ -11,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 {
|
||||
|
@ -64,6 +69,7 @@ impl InnerCtx {
|
|||
baseimg: conn.get_pool(&config.storage.base_image_pool).await?,
|
||||
};
|
||||
|
||||
trace!("Connecting to database");
|
||||
let db_uri = config.db_uri.clone();
|
||||
let sqldb = tokio::task::spawn_blocking(|| {
|
||||
let manager = ConnectionManager::<SqliteConnection>::new(db_uri);
|
||||
|
@ -74,6 +80,7 @@ impl InnerCtx {
|
|||
.unwrap()?;
|
||||
|
||||
{
|
||||
trace!("Running pending migrations");
|
||||
let mut conn = sqldb.get()?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
conn.run_pending_migrations(MIGRATIONS)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -11,11 +11,5 @@ tokio = { version = "1.39.2", features = ["rt-multi-thread", "net", "macros"] }
|
|||
nzr-api = { path = "../nzr-api" }
|
||||
tracing = { version = "0.1.40", features = ["log"] }
|
||||
tracing-subscriber = "0.3.18"
|
||||
tarpc = { version = "0.34", features = [
|
||||
"tokio1",
|
||||
"unix",
|
||||
"serde-transport",
|
||||
"serde-transport-bincode",
|
||||
] }
|
||||
moka = { version = "0.12.8", features = ["future"] }
|
||||
anyhow = "1.0.86"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::hash::RandomState;
|
||||
use std::net::IpAddr;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::Context as _;
|
||||
|
@ -10,7 +11,6 @@ use nzr_api::{
|
|||
net::mac::MacAddr,
|
||||
NazrinClient,
|
||||
};
|
||||
use tarpc::{tokio_serde::formats::Bincode, tokio_util::codec::LengthDelimitedCodec};
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
|
@ -26,7 +26,7 @@ impl Context {
|
|||
async fn hydrate_hosts(&self) -> Result<()> {
|
||||
let instances = self
|
||||
.api_client
|
||||
.get_instances(tarpc::context::current(), false)
|
||||
.get_instances(nzr_api::default_ctx(), false)
|
||||
.await?
|
||||
.map_err(|e| anyhow::anyhow!("nzrd error: {e}"))?;
|
||||
|
||||
|
@ -52,7 +52,7 @@ impl Context {
|
|||
async fn hydrate_nets(&self) -> Result<()> {
|
||||
let subnets = self
|
||||
.api_client
|
||||
.get_subnets(tarpc::context::current())
|
||||
.get_subnets(nzr_api::default_ctx())
|
||||
.await?
|
||||
.map_err(|e| anyhow::anyhow!("nzrd error: {e}"))?;
|
||||
|
||||
|
@ -68,21 +68,19 @@ impl Context {
|
|||
let sock = UnixStream::connect(&cfg.rpc.socket_path)
|
||||
.await
|
||||
.context("Connection to nzrd failed")?;
|
||||
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();
|
||||
nzr_api::new_client(sock)
|
||||
};
|
||||
|
||||
let listen_addr: SocketAddr = cfg
|
||||
.dhcp
|
||||
.listen_addr
|
||||
.parse()
|
||||
.context("Malformed listen address")?;
|
||||
let listen_addr: SocketAddr = {
|
||||
let ip: IpAddr = cfg
|
||||
.dhcp
|
||||
.listen_addr
|
||||
.parse()
|
||||
.context("Malformed listen address")?;
|
||||
(ip, cfg.dhcp.port).into()
|
||||
};
|
||||
|
||||
let server_sock = UdpSocket::bind(&listen_addr)
|
||||
let server_sock = UdpSocket::bind(listen_addr)
|
||||
.await
|
||||
.context("Unable to listen")?;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{net::Ipv4Addr, process::ExitCode};
|
|||
use ctx::Context;
|
||||
use dhcproto::{
|
||||
v4::{DhcpOption, Message, MessageType, Opcode, OptionCode},
|
||||
Decodable, Decoder,
|
||||
Decodable, Decoder, Encodable, Encoder,
|
||||
};
|
||||
use nzr_api::{config::Config, net::mac::MacAddr};
|
||||
use std::net::SocketAddr;
|
||||
|
@ -16,9 +16,9 @@ const DEFAULT_LEASE: u32 = 86400;
|
|||
|
||||
fn make_reply(msg: &Message, msg_type: MessageType, lease_addr: Option<Ipv4Addr>) -> Message {
|
||||
let mut resp = Message::new(
|
||||
EMPTY_V4,
|
||||
EMPTY_V4,
|
||||
lease_addr.unwrap_or(EMPTY_V4),
|
||||
EMPTY_V4,
|
||||
msg.giaddr(),
|
||||
msg.chaddr(),
|
||||
);
|
||||
|
@ -46,6 +46,7 @@ async fn handle_message(ctx: &Context, from: SocketAddr, msg: &Message) {
|
|||
tracing::info!("Received DHCP payload with invalid addr (different media type?)");
|
||||
return;
|
||||
};
|
||||
tracing::debug!("Client MAC is {client_mac}!!!");
|
||||
|
||||
let instance = match ctx.instance_by_mac(client_mac).await {
|
||||
Ok(Some(i)) => i,
|
||||
|
@ -59,6 +60,11 @@ async fn handle_message(ctx: &Context, from: SocketAddr, msg: &Message) {
|
|||
}
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
"Recieved {msg_type:?} from {client_mac} (assuming {})",
|
||||
&instance.name
|
||||
);
|
||||
|
||||
let mut lease_time = None;
|
||||
let mut nak = false;
|
||||
|
||||
|
@ -68,12 +74,18 @@ async fn handle_message(ctx: &Context, from: SocketAddr, msg: &Message) {
|
|||
make_reply(msg, MessageType::Offer, Some(instance.lease.addr.addr))
|
||||
}
|
||||
MessageType::Request => {
|
||||
if msg.ciaddr() != instance.lease.addr.addr {
|
||||
if let Some(DhcpOption::RequestedIpAddress(addr)) =
|
||||
msg.opts().get(OptionCode::RequestedIpAddress)
|
||||
{
|
||||
if *addr == instance.lease.addr.addr {
|
||||
make_reply(msg, MessageType::Ack, Some(instance.lease.addr.addr))
|
||||
} else {
|
||||
nak = true;
|
||||
make_reply(msg, MessageType::Nak, None)
|
||||
}
|
||||
} else {
|
||||
nak = true;
|
||||
make_reply(msg, MessageType::Nak, None)
|
||||
} else {
|
||||
lease_time = Some(DEFAULT_LEASE);
|
||||
make_reply(msg, MessageType::Ack, Some(instance.lease.addr.addr))
|
||||
}
|
||||
}
|
||||
MessageType::Decline => {
|
||||
|
@ -86,57 +98,80 @@ async fn handle_message(ctx: &Context, from: SocketAddr, msg: &Message) {
|
|||
}
|
||||
MessageType::Release => {
|
||||
// We only provide static leases
|
||||
tracing::trace!("Ignoring DHCPRELEASE");
|
||||
tracing::debug!("Ignoring DHCPRELEASE");
|
||||
return;
|
||||
}
|
||||
MessageType::Inform => make_reply(msg, MessageType::Ack, None),
|
||||
other => {
|
||||
tracing::trace!("Received unhandled message {other:?}");
|
||||
tracing::info!("Received unhandled message {other:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let opts = response.opts_mut();
|
||||
let giaddr = if msg.giaddr().is_unspecified() {
|
||||
todo!("no relay??")
|
||||
} else {
|
||||
msg.giaddr()
|
||||
};
|
||||
|
||||
opts.insert(DhcpOption::ServerIdentifier(giaddr));
|
||||
if let Some(time) = lease_time {
|
||||
opts.insert(DhcpOption::AddressLeaseTime(time));
|
||||
}
|
||||
|
||||
if !nak {
|
||||
// Get general networking info
|
||||
let subnet = match ctx.get_subnet(&instance.lease.subnet).await {
|
||||
Ok(Some(net)) => net,
|
||||
Ok(None) => {
|
||||
tracing::error!("nzrd says '{}' isn't a subnet", &instance.lease.subnet);
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Error getting subnet: {err}");
|
||||
return;
|
||||
}
|
||||
{
|
||||
let opts = response.opts_mut();
|
||||
let giaddr = if msg.giaddr().is_unspecified() {
|
||||
todo!("no relay??")
|
||||
} else {
|
||||
msg.giaddr()
|
||||
};
|
||||
|
||||
opts.insert(DhcpOption::Hostname(instance.name.clone()));
|
||||
|
||||
if !subnet.dns.is_empty() {
|
||||
opts.insert(DhcpOption::DomainNameServer(subnet.dns));
|
||||
opts.insert(DhcpOption::ServerIdentifier(giaddr));
|
||||
if let Some(time) = lease_time {
|
||||
opts.insert(DhcpOption::AddressLeaseTime(time));
|
||||
}
|
||||
|
||||
if let Some(name) = subnet.domain_name {
|
||||
opts.insert(DhcpOption::DomainName(name.to_utf8()));
|
||||
}
|
||||
if !nak {
|
||||
// Get general networking info
|
||||
let subnet = match ctx.get_subnet(&instance.lease.subnet).await {
|
||||
Ok(Some(net)) => net,
|
||||
Ok(None) => {
|
||||
tracing::error!("nzrd says '{}' isn't a subnet", &instance.lease.subnet);
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Error getting subnet: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(gw) = subnet.gateway4 {
|
||||
opts.insert(DhcpOption::Router(Vec::from(&[gw])));
|
||||
}
|
||||
opts.insert(DhcpOption::Hostname(instance.name.clone()));
|
||||
|
||||
opts.insert(DhcpOption::SubnetMask(instance.lease.addr.netmask()));
|
||||
if !subnet.dns.is_empty() {
|
||||
opts.insert(DhcpOption::DomainNameServer(subnet.dns));
|
||||
}
|
||||
|
||||
if let Some(name) = subnet.domain_name {
|
||||
opts.insert(DhcpOption::DomainName(name.to_utf8()));
|
||||
}
|
||||
|
||||
if let Some(gw) = subnet.gateway4 {
|
||||
opts.insert(DhcpOption::Router(Vec::from(&[gw])));
|
||||
}
|
||||
|
||||
opts.insert(DhcpOption::SubnetMask(instance.lease.addr.netmask()));
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Sending message {:?} with yiaddr {}",
|
||||
response
|
||||
.opts()
|
||||
.get(OptionCode::MessageType)
|
||||
.unwrap_or(&DhcpOption::End),
|
||||
response.yiaddr()
|
||||
);
|
||||
|
||||
// unicast it back
|
||||
let mut resp_buf = Vec::new();
|
||||
let mut enc = Encoder::new(&mut resp_buf);
|
||||
if let Err(err) = response.encode(&mut enc) {
|
||||
tracing::error!("Couldn't encode response: {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = ctx.sock().send_to(&resp_buf, from).await {
|
||||
tracing::error!("Couldn't send response: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
14
omyacid/Cargo.toml
Normal file
14
omyacid/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "omyacid"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
nzr-api = { path = "../nzr-api" }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
axum = "0.7"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
anyhow = "1"
|
||||
askama = "0.12"
|
||||
moka = { version = "0.12.8", features = ["future"] }
|
95
omyacid/src/ctx.rs
Normal file
95
omyacid/src/ctx.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use std::hash::RandomState;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Result;
|
||||
use moka::future::Cache;
|
||||
use nzr_api::config::Config;
|
||||
use nzr_api::model::Instance;
|
||||
use nzr_api::InstanceQuery;
|
||||
use nzr_api::NazrinClient;
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InstanceMeta {
|
||||
pub inst: Instance,
|
||||
pub userdata: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
api_client: NazrinClient,
|
||||
config: Arc<Config>,
|
||||
host_cache: Cache<Ipv4Addr, InstanceMeta, RandomState>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub async fn new(cfg: Config) -> Result<Self> {
|
||||
let api_client = {
|
||||
let sock = UnixStream::connect(&cfg.rpc.socket_path)
|
||||
.await
|
||||
.context("Connection to nzrd failed")?;
|
||||
nzr_api::new_client(sock)
|
||||
};
|
||||
|
||||
let host_cache = Cache::builder()
|
||||
.time_to_live(Duration::from_secs(15))
|
||||
.max_capacity(5)
|
||||
.build();
|
||||
|
||||
Ok(Self {
|
||||
api_client,
|
||||
host_cache,
|
||||
config: Arc::new(cfg),
|
||||
})
|
||||
}
|
||||
|
||||
// Internal function to hydrate the instance metadata, if needed
|
||||
async fn get_instmeta(&self, addr: Ipv4Addr) -> Result<Option<InstanceMeta>> {
|
||||
if let Some(meta) = self.host_cache.get(&addr).await {
|
||||
tracing::debug!("Cache hit!");
|
||||
Ok(Some(meta))
|
||||
} else {
|
||||
let inst = self
|
||||
.api_client
|
||||
.find_instance(nzr_api::default_ctx(), InstanceQuery::Ipv4Addr(addr))
|
||||
.await
|
||||
.context("RPC error")?
|
||||
.map_err(|e| anyhow::anyhow!("nzrd error: {e}"))?;
|
||||
if let Some(inst) = inst {
|
||||
let userdata = self
|
||||
.api_client
|
||||
.get_instance_userdata(nzr_api::default_ctx(), inst.id)
|
||||
.await
|
||||
.context("RPC error")?
|
||||
.map_err(|e| anyhow::anyhow!("nzrd error: {e}"))?;
|
||||
|
||||
let meta = InstanceMeta { inst, userdata };
|
||||
|
||||
self.host_cache.insert(addr, meta.clone()).await;
|
||||
|
||||
Ok(Some(meta))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_instance(&self, addr: Ipv4Addr) -> Result<Option<Instance>> {
|
||||
self.get_instmeta(addr)
|
||||
.await
|
||||
.map(|opt| opt.map(|im| im.inst))
|
||||
}
|
||||
|
||||
pub async fn get_inst_userdata(&self, addr: Ipv4Addr) -> Result<Option<Vec<u8>>> {
|
||||
self.get_instmeta(addr)
|
||||
.await
|
||||
.map(|opt| opt.map(|im| im.userdata))
|
||||
}
|
||||
|
||||
pub fn cfg(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
}
|
120
omyacid/src/main.rs
Normal file
120
omyacid/src/main.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
mod ctx;
|
||||
mod model;
|
||||
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
process::ExitCode,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
extract::{ConnectInfo, State},
|
||||
http::StatusCode,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use model::Metadata;
|
||||
use nzr_api::config::Config;
|
||||
|
||||
async fn get_meta_data(
|
||||
State(ctx): State<ctx::Context>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
) -> Result<String, StatusCode> {
|
||||
if let IpAddr::V4(ip) = addr.ip() {
|
||||
match ctx.get_instance(ip).await {
|
||||
Ok(Some(inst)) => {
|
||||
let meta = Metadata {
|
||||
inst_name: &inst.name,
|
||||
ssh_pubkeys: Vec::new(), // TODO
|
||||
username: Some(ctx.cfg().cloud.admin_user.as_ref()),
|
||||
};
|
||||
|
||||
meta.render().map_err(|e| {
|
||||
tracing::error!("Renderer error: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})
|
||||
}
|
||||
Ok(None) => {
|
||||
tracing::warn!("Request from unregistered server {ip}");
|
||||
Err(StatusCode::FORBIDDEN)
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("{err}");
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_user_data(
|
||||
State(ctx): State<ctx::Context>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
) -> Result<Vec<u8>, StatusCode> {
|
||||
if let IpAddr::V4(ip) = addr.ip() {
|
||||
match ctx.get_inst_userdata(ip).await {
|
||||
Ok(Some(data)) => Ok(data),
|
||||
Ok(None) => {
|
||||
tracing::warn!("Request from unregistered server {ip}");
|
||||
Err(StatusCode::FORBIDDEN)
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("{err}");
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> ExitCode {
|
||||
tracing_subscriber::fmt().init();
|
||||
let cfg: Config = match Config::figment().extract() {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
tracing::error!("Unable to get configuration: {err}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let http_sock = {
|
||||
let addr = match IpAddr::from_str(&cfg.cloud.listen_addr) {
|
||||
Ok(addr) => addr,
|
||||
Err(err) => {
|
||||
tracing::error!("Invalid listen IP address ({err})");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
match tokio::net::TcpListener::bind((addr, cfg.cloud.port)).await {
|
||||
Ok(sock) => sock,
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to bind to {addr}:{}: {err}", cfg.cloud.port);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ctx = match ctx::Context::new(cfg).await {
|
||||
Ok(ctx) => ctx,
|
||||
Err(err) => {
|
||||
tracing::error!("{err}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/meta-data", get(get_meta_data))
|
||||
.route("/user-data", get(get_user_data))
|
||||
.with_state(ctx);
|
||||
|
||||
if let Err(err) = axum::serve(http_sock, app).await {
|
||||
tracing::error!("axum error: {err}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
8
omyacid/src/model.rs
Normal file
8
omyacid/src/model.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use askama::Template;
|
||||
#[derive(Template)]
|
||||
#[template(path = "meta-data.yml")]
|
||||
pub struct Metadata<'a> {
|
||||
pub inst_name: &'a str,
|
||||
pub ssh_pubkeys: Vec<&'a String>,
|
||||
pub username: Option<&'a str>,
|
||||
}
|
11
omyacid/templates/meta-data.yml
Normal file
11
omyacid/templates/meta-data.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
instance_id: "iid-{{ inst_name }}"
|
||||
local_hostname: "{{ inst_name }}"
|
||||
{% if !ssh_pubkeys.is_empty() -%}
|
||||
public_keys:
|
||||
{% for key in ssh_pubkeys -%}
|
||||
- "{{ key }}"
|
||||
{% endfor %}
|
||||
{% endif -%}
|
||||
{% if let Some(user) = username -%}
|
||||
default_username: "{{ user }}"
|
||||
{%- endif %}
|
Loading…
Reference in a new issue