Add healthcheck for db, new clippy lints

This commit is contained in:
asonix 2023-01-29 13:21:36 -06:00
parent 96547230bc
commit f9cad61049
13 changed files with 78 additions and 46 deletions

View file

@ -5,7 +5,7 @@ fn git_info() {
if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() { if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() {
if output.status.success() { if output.status.success() {
let git_hash = String::from_utf8_lossy(&output.stdout); let git_hash = String::from_utf8_lossy(&output.stdout);
println!("cargo:rustc-env=GIT_HASH={}", git_hash); println!("cargo:rustc-env=GIT_HASH={git_hash}");
println!("cargo:rustc-env=GIT_SHORT_HASH={}", &git_hash[..8]) println!("cargo:rustc-env=GIT_SHORT_HASH={}", &git_hash[..8])
} }
} }
@ -16,7 +16,7 @@ fn git_info() {
{ {
if output.status.success() { if output.status.success() {
let git_branch = String::from_utf8_lossy(&output.stdout); let git_branch = String::from_utf8_lossy(&output.stdout);
println!("cargo:rustc-env=GIT_BRANCH={}", git_branch); println!("cargo:rustc-env=GIT_BRANCH={git_branch}");
} }
} }
} }
@ -32,11 +32,11 @@ fn version_info() -> Result<(), anyhow::Error> {
let data: toml::Value = toml::from_str(&cargo_data)?; let data: toml::Value = toml::from_str(&cargo_data)?;
if let Some(version) = data["package"]["version"].as_str() { if let Some(version) = data["package"]["version"].as_str() {
println!("cargo:rustc-env=PKG_VERSION={}", version); println!("cargo:rustc-env=PKG_VERSION={version}");
} }
if let Some(name) = data["package"]["name"].as_str() { if let Some(name) = data["package"]["name"].as_str() {
println!("cargo:rustc-env=PKG_NAME={}", name); println!("cargo:rustc-env=PKG_NAME={name}");
} }
Ok(()) Ok(())

View file

@ -40,11 +40,11 @@ impl std::fmt::Display for Counter {
let labels = self let labels = self
.labels .labels
.iter() .iter()
.map(|(k, v)| format!("{}: {}", k, v)) .map(|(k, v)| format!("{k}: {v}"))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
write!(f, "{} - {}", labels, self.value) write!(f, "{labels} - {}", self.value)
} }
} }
@ -59,11 +59,11 @@ impl std::fmt::Display for Gauge {
let labels = self let labels = self
.labels .labels
.iter() .iter()
.map(|(k, v)| format!("{}: {}", k, v)) .map(|(k, v)| format!("{k}: {v}"))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
write!(f, "{} - {}", labels, self.value) write!(f, "{labels} - {}", self.value)
} }
} }
@ -78,7 +78,7 @@ impl std::fmt::Display for Histogram {
let labels = self let labels = self
.labels .labels
.iter() .iter()
.map(|(k, v)| format!("{}: {}", k, v)) .map(|(k, v)| format!("{k}: {v}"))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
@ -87,15 +87,15 @@ impl std::fmt::Display for Histogram {
.iter() .iter()
.map(|(k, v)| { .map(|(k, v)| {
if let Some(v) = v { if let Some(v) = v {
format!("{}: {:.6}", k, v) format!("{k}: {v:.6}")
} else { } else {
format!("{}: None,", k) format!("{k}: None,")
} }
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
write!(f, "{} - {}", labels, value) write!(f, "{labels} - {value}")
} }
} }
@ -172,18 +172,18 @@ impl Snapshot {
continue; continue;
} }
println!("\t{}", key); println!("\t{key}");
for counter in counters { for counter in counters {
println!("\t\t{}", counter); println!("\t\t{counter}");
} }
} }
for (key, counters) in merging { for (key, counters) in merging {
println!("\t{}", key); println!("\t{key}");
for (_, counter) in counters { for (_, counter) in counters {
if let Some(counter) = counter.merge() { if let Some(counter) = counter.merge() {
println!("\t\t{}", counter); println!("\t\t{counter}");
} }
} }
} }
@ -192,10 +192,10 @@ impl Snapshot {
if !self.gauges.is_empty() { if !self.gauges.is_empty() {
println!("Gauges"); println!("Gauges");
for (key, gauges) in self.gauges { for (key, gauges) in self.gauges {
println!("\t{}", key); println!("\t{key}");
for gauge in gauges { for gauge in gauges {
println!("\t\t{}", gauge); println!("\t\t{gauge}");
} }
} }
} }
@ -203,10 +203,10 @@ impl Snapshot {
if !self.histograms.is_empty() { if !self.histograms.is_empty() {
println!("Histograms"); println!("Histograms");
for (key, histograms) in self.histograms { for (key, histograms) in self.histograms {
println!("\t{}", key); println!("\t{key}");
for histogram in histograms { for histogram in histograms {
println!("\t\t{}", histogram); println!("\t\t{histogram}");
} }
} }
} }

View file

@ -170,7 +170,7 @@ impl Config {
let config: ParsedConfig = config.try_deserialize()?; let config: ParsedConfig = config.try_deserialize()?;
let scheme = if config.https { "https" } else { "http" }; let scheme = if config.https { "https" } else { "http" };
let base_uri = iri!(format!("{}://{}", scheme, config.hostname)).into_absolute(); let base_uri = iri!(format!("{scheme}://{}", config.hostname)).into_absolute();
let tls = match (config.tls_key, config.tls_cert) { let tls = match (config.tls_key, config.tls_cert) {
(Some(key), Some(cert)) => Some(TlsConfig { key, cert }), (Some(key), Some(cert)) => Some(TlsConfig { key, cert }),
@ -207,8 +207,8 @@ impl Config {
let source_url = match Self::git_hash() { let source_url = match Self::git_hash() {
Some(hash) => format!( Some(hash) => format!(
"{}{}{}", "{}{}{hash}",
config.source_repo, config.repository_commit_base, hash config.source_repo, config.repository_commit_base
) )
.parse() .parse()
.expect("constructed source URL is valid"), .expect("constructed source URL is valid"),
@ -332,7 +332,7 @@ impl Config {
match AdminConfig::build(api_token) { match AdminConfig::build(api_token) {
Ok(conf) => Some(actix_web::web::Data::new(conf)), Ok(conf) => Some(actix_web::web::Data::new(conf)),
Err(e) => { Err(e) => {
tracing::error!("Error creating admin config: {}", e); tracing::error!("Error creating admin config: {e}");
None None
} }
} }
@ -371,7 +371,7 @@ impl Config {
pub(crate) fn software_version() -> String { pub(crate) fn software_version() -> String {
if let Some(git) = Self::git_version() { if let Some(git) = Self::git_version() {
return format!("v{}-{}", Self::version(), git); return format!("v{}-{git}", Self::version());
} }
format!("v{}", Self::version()) format!("v{}", Self::version())
@ -381,7 +381,7 @@ impl Config {
let branch = Self::git_branch()?; let branch = Self::git_branch()?;
let hash = Self::git_short_hash()?; let hash = Self::git_short_hash()?;
Some(format!("{}-{}", branch, hash)) Some(format!("{branch}-{hash}"))
} }
fn name() -> &'static str { fn name() -> &'static str {
@ -463,7 +463,7 @@ impl Config {
resolved resolved
} }
UrlKind::Media(uuid) => FixedBaseResolver::new(self.base_uri.as_ref()) UrlKind::Media(uuid) => FixedBaseResolver::new(self.base_uri.as_ref())
.resolve(IriRelativeStr::new(&format!("media/{}", uuid))?.as_ref()) .resolve(IriRelativeStr::new(&format!("media/{uuid}"))?.as_ref())
.try_to_dedicated_string()?, .try_to_dedicated_string()?,
UrlKind::NodeInfo => FixedBaseResolver::new(self.base_uri.as_ref()) UrlKind::NodeInfo => FixedBaseResolver::new(self.base_uri.as_ref())
.resolve(IriRelativeStr::new("nodeinfo/2.0.json")?.as_ref()) .resolve(IriRelativeStr::new("nodeinfo/2.0.json")?.as_ref())

View file

@ -182,7 +182,7 @@ impl Node {
let authority = url.authority_str().ok_or(ErrorKind::MissingDomain)?; let authority = url.authority_str().ok_or(ErrorKind::MissingDomain)?;
let scheme = url.scheme_str(); let scheme = url.scheme_str();
let base = iri!(format!("{}://{}", scheme, authority)); let base = iri!(format!("{scheme}://{authority}"));
Ok(Node { Ok(Node {
base, base,

View file

@ -10,7 +10,10 @@ use rsa::{
use sled::{Batch, Tree}; use sled::{Batch, Tree};
use std::{ use std::{
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
sync::Arc, sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
time::SystemTime, time::SystemTime,
}; };
use time::OffsetDateTime; use time::OffsetDateTime;
@ -22,6 +25,8 @@ pub(crate) struct Db {
} }
struct Inner { struct Inner {
healthz: Tree,
healthz_counter: Arc<AtomicU64>,
actor_id_actor: Tree, actor_id_actor: Tree,
public_key_id_actor_id: Tree, public_key_id_actor_id: Tree,
connected_actor_ids: Tree, connected_actor_ids: Tree,
@ -242,6 +247,8 @@ impl Db {
fn build_inner(restricted_mode: bool, db: sled::Db) -> Result<Self, Error> { fn build_inner(restricted_mode: bool, db: sled::Db) -> Result<Self, Error> {
Ok(Db { Ok(Db {
inner: Arc::new(Inner { inner: Arc::new(Inner {
healthz: db.open_tree("healthz")?,
healthz_counter: Arc::new(AtomicU64::new(0)),
actor_id_actor: db.open_tree("actor-id-actor")?, actor_id_actor: db.open_tree("actor-id-actor")?,
public_key_id_actor_id: db.open_tree("public-key-id-actor-id")?, public_key_id_actor_id: db.open_tree("public-key-id-actor-id")?,
connected_actor_ids: db.open_tree("connected-actor-ids")?, connected_actor_ids: db.open_tree("connected-actor-ids")?,
@ -273,6 +280,21 @@ impl Db {
Ok(t) Ok(t)
} }
pub(crate) async fn check_health(&self) -> Result<(), Error> {
let next = self.inner.healthz_counter.fetch_add(1, Ordering::Relaxed);
self.unblock(move |inner| {
inner
.healthz
.insert("healthz", &next.to_be_bytes()[..])
.map_err(Error::from)
})
.await?;
self.inner.healthz.flush_async().await?;
self.unblock(move |inner| inner.healthz.get("healthz").map_err(Error::from))
.await?;
Ok(())
}
pub(crate) async fn mark_last_seen( pub(crate) async fn mark_last_seen(
&self, &self,
nodes: HashMap<String, OffsetDateTime>, nodes: HashMap<String, OffsetDateTime>,
@ -468,7 +490,7 @@ impl Db {
pub(crate) async fn is_connected(&self, base_id: IriString) -> Result<bool, Error> { pub(crate) async fn is_connected(&self, base_id: IriString) -> Result<bool, Error> {
let scheme = base_id.scheme_str(); let scheme = base_id.scheme_str();
let authority = base_id.authority_str().ok_or(ErrorKind::MissingDomain)?; let authority = base_id.authority_str().ok_or(ErrorKind::MissingDomain)?;
let prefix = format!("{}://{}", scheme, authority); let prefix = format!("{scheme}://{authority}");
self.unblock(move |inner| { self.unblock(move |inner| {
let connected = inner let connected = inner
@ -528,7 +550,7 @@ impl Db {
} }
pub(crate) async fn remove_connection(&self, actor_id: IriString) -> Result<(), Error> { pub(crate) async fn remove_connection(&self, actor_id: IriString) -> Result<(), Error> {
tracing::debug!("Removing Connection: {}", actor_id); tracing::debug!("Removing Connection: {actor_id}");
self.unblock(move |inner| { self.unblock(move |inner| {
inner inner
.connected_actor_ids .connected_actor_ids
@ -540,7 +562,7 @@ impl Db {
} }
pub(crate) async fn add_connection(&self, actor_id: IriString) -> Result<(), Error> { pub(crate) async fn add_connection(&self, actor_id: IriString) -> Result<(), Error> {
tracing::debug!("Adding Connection: {}", actor_id); tracing::debug!("Adding Connection: {actor_id}");
self.unblock(move |inner| { self.unblock(move |inner| {
inner inner
.connected_actor_ids .connected_actor_ids

View file

@ -45,7 +45,7 @@ impl QueryInstance {
.authority_str() .authority_str()
.ok_or(ErrorKind::MissingDomain)?; .ok_or(ErrorKind::MissingDomain)?;
let scheme = self.actor_id.scheme_str(); let scheme = self.actor_id.scheme_str();
let instance_uri = iri!(format!("{}://{}/api/v1/instance", scheme, authority)); let instance_uri = iri!(format!("{scheme}://{authority}/api/v1/instance"));
let instance = match state let instance = match state
.requests .requests

View file

@ -39,7 +39,7 @@ impl QueryNodeinfo {
.authority_str() .authority_str()
.ok_or(ErrorKind::MissingDomain)?; .ok_or(ErrorKind::MissingDomain)?;
let scheme = self.actor_id.scheme_str(); let scheme = self.actor_id.scheme_str();
let well_known_uri = iri!(format!("{}://{}/.well-known/nodeinfo", scheme, authority)); let well_known_uri = iri!(format!("{scheme}://{authority}/.well-known/nodeinfo"));
let well_known = match state let well_known = match state
.requests .requests
@ -168,7 +168,7 @@ impl<'de> serde::de::Visitor<'de> for SupportedVersionVisitor {
type Value = SupportedVersion; type Value = SupportedVersion;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a string starting with '{}'", SUPPORTED_VERSIONS) write!(f, "a string starting with '{SUPPORTED_VERSIONS}'")
} }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
@ -187,7 +187,7 @@ impl<'de> serde::de::Visitor<'de> for SupportedNodeinfoVisitor {
type Value = SupportedNodeinfo; type Value = SupportedNodeinfo;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a string starting with '{}'", SUPPORTED_NODEINFO) write!(f, "a string starting with '{SUPPORTED_NODEINFO}'")
} }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>

View file

@ -39,7 +39,7 @@ use self::{
db::Db, db::Db,
jobs::create_workers, jobs::create_workers,
middleware::{DebugPayload, MyVerify, RelayResolver, Timings}, middleware::{DebugPayload, MyVerify, RelayResolver, Timings},
routes::{actor, inbox, index, nodeinfo, nodeinfo_meta, statics}, routes::{actor, healthz, inbox, index, nodeinfo, nodeinfo_meta, statics},
}; };
fn init_subscriber( fn init_subscriber(
@ -273,6 +273,7 @@ async fn do_server_main(
app.wrap(Compress::default()) app.wrap(Compress::default())
.wrap(TracingLogger::default()) .wrap(TracingLogger::default())
.wrap(Timings) .wrap(Timings)
.route("/healthz", web::get().to(healthz))
.service(web::resource("/").route(web::get().to(index))) .service(web::resource("/").route(web::get().to(index)))
.service(web::resource("/media/{path}").route(web::get().to(routes::media))) .service(web::resource("/media/{path}").route(web::get().to(routes::media)))
.service( .service(

View file

@ -75,7 +75,7 @@ impl MyVerify {
Ok(res) => res.actor_id().ok_or(ErrorKind::MissingId), Ok(res) => res.actor_id().ok_or(ErrorKind::MissingId),
Err(e) => { Err(e) => {
if e.is_gone() { if e.is_gone() {
tracing::warn!("Actor gone: {}", public_key_id); tracing::warn!("Actor gone: {public_key_id}");
return Ok(false); return Ok(false);
} else { } else {
return Err(e); return Err(e);
@ -178,13 +178,13 @@ mod tests {
#[test] #[test]
fn handles_masto_keys() { fn handles_masto_keys() {
println!("{}", ASONIX_DOG_KEY); println!("{ASONIX_DOG_KEY}");
let _ = RsaPublicKey::from_public_key_pem(ASONIX_DOG_KEY.trim()).unwrap(); let _ = RsaPublicKey::from_public_key_pem(ASONIX_DOG_KEY.trim()).unwrap();
} }
#[test] #[test]
fn handles_pleromo_keys() { fn handles_pleromo_keys() {
println!("{}", KARJALAZET_KEY); println!("{KARJALAZET_KEY}");
let _ = RsaPublicKey::from_public_key_pem(KARJALAZET_KEY.trim()).unwrap(); let _ = RsaPublicKey::from_public_key_pem(KARJALAZET_KEY.trim()).unwrap();
} }

View file

@ -61,7 +61,7 @@ impl Breakers {
if let Some(mut breaker) = self.inner.get_mut(authority) { if let Some(mut breaker) = self.inner.get_mut(authority) {
breaker.fail(); breaker.fail();
if !breaker.should_try() { if !breaker.should_try() {
tracing::warn!("Failed breaker for {}", authority); tracing::warn!("Failed breaker for {authority}");
} }
false false
} else { } else {
@ -235,7 +235,7 @@ impl Requests {
if let Ok(bytes) = res.body().await { if let Ok(bytes) = res.body().await {
if let Ok(s) = String::from_utf8(bytes.as_ref().to_vec()) { if let Ok(s) = String::from_utf8(bytes.as_ref().to_vec()) {
if !s.is_empty() { if !s.is_empty() {
tracing::warn!("Response from {}, {}", parsed_url, s); tracing::warn!("Response from {parsed_url}, {s}");
} }
} }
} }

View file

@ -1,4 +1,5 @@
mod actor; mod actor;
mod healthz;
mod inbox; mod inbox;
mod index; mod index;
mod media; mod media;
@ -7,6 +8,7 @@ mod statics;
pub(crate) use self::{ pub(crate) use self::{
actor::route as actor, actor::route as actor,
healthz::route as healthz,
inbox::route as inbox, inbox::route as inbox,
index::route as index, index::route as index,
media::route as media, media::route as media,

7
src/routes/healthz.rs Normal file
View file

@ -0,0 +1,7 @@
use crate::{data::State, error::Error};
use actix_web::{web, HttpResponse};
pub(crate) async fn route(state: web::Data<State>) -> Result<HttpResponse, Error> {
state.db.check_health().await?;
Ok(HttpResponse::Ok().finish())
}

View file

@ -89,19 +89,19 @@ async fn answer(bot: Bot, msg: Message, cmd: Command, db: Db) -> ResponseResult<
.await?; .await?;
} }
Command::Block { domain } if db.add_blocks(vec![domain.clone()]).await.is_ok() => { Command::Block { domain } if db.add_blocks(vec![domain.clone()]).await.is_ok() => {
bot.send_message(msg.chat.id, format!("{} has been blocked", domain)) bot.send_message(msg.chat.id, format!("{domain} has been blocked"))
.await?; .await?;
} }
Command::Unblock { domain } if db.remove_blocks(vec![domain.clone()]).await.is_ok() => { Command::Unblock { domain } if db.remove_blocks(vec![domain.clone()]).await.is_ok() => {
bot.send_message(msg.chat.id, format!("{} has been unblocked", domain)) bot.send_message(msg.chat.id, format!("{domain} has been unblocked"))
.await?; .await?;
} }
Command::Allow { domain } if db.add_allows(vec![domain.clone()]).await.is_ok() => { Command::Allow { domain } if db.add_allows(vec![domain.clone()]).await.is_ok() => {
bot.send_message(msg.chat.id, format!("{} has been allowed", domain)) bot.send_message(msg.chat.id, format!("{domain} has been allowed"))
.await?; .await?;
} }
Command::Disallow { domain } if db.remove_allows(vec![domain.clone()]).await.is_ok() => { Command::Disallow { domain } if db.remove_allows(vec![domain.clone()]).await.is_ok() => {
bot.send_message(msg.chat.id, format!("{} has been disallowed", domain)) bot.send_message(msg.chat.id, format!("{domain} has been disallowed"))
.await?; .await?;
} }
Command::ListAllowed => { Command::ListAllowed => {