websocket done, api needs rebuild

This commit is contained in:
41666 2022-12-07 23:42:19 -05:00
parent 5c3a9a1888
commit 50c4ac387a
13 changed files with 192 additions and 195 deletions

View file

@ -6,13 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
redis = { version = "0.22.1", features = ["aio", "r2d2", "tokio-comp"] }
serde_json = "1.0.89"
serde = "1.0.149"
async-graphql = { version = "5.0.2" }
async-graphql-axum = "5.0.2"
axum = "0.6.1"
sqlx = { version = "0.6.2", features = [ "runtime-tokio-native-tls" , "postgres" ] }
sqlx = { version = "0.6.2", features = [ "runtime-tokio-native-tls", "postgres" ] }
tokio = { version = "1.23.0", features = [ "full" ] }
tower-http = { version = "0.3.5", features = ["cors"] }
lazy_static = "1.4.0"

View file

@ -1,6 +1,4 @@
use crate::util::zcount;
use async_graphql::{Context, Object};
use redis::aio::MultiplexedConnection;
pub struct Classes {
world_id: String,
@ -11,8 +9,7 @@ impl Classes {
Self { world_id }
}
async fn by_class<'ctx>(&self, ctx: &Context<'ctx>, class_name: &str) -> u32 {
let con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
zcount(con, format!("c:{}/{}", self.world_id, class_name)).await
0
}
}

View file

@ -1,39 +1,45 @@
use async_graphql::{Context, Enum, Object};
use axum::{http::StatusCode, response::IntoResponse, Extension, Json};
use redis::{aio::MultiplexedConnection, pipe};
use sqlx::{query, Pool, Postgres, Row};
pub async fn get_health(
Extension(mut redis): Extension<redis::aio::MultiplexedConnection>,
) -> impl IntoResponse {
let (ping, pc, ps4us, ps4eu): (String, bool, bool, bool) = pipe()
.cmd("PING")
.get("heartbeat:pc")
.get("heartbeat:ps4us")
.get("heartbeat:ps4eu")
.query_async(&mut redis)
.await
.unwrap_or_default();
pub async fn get_health(Extension(pool): Extension<Pool<Postgres>>) -> impl IntoResponse {
let events_resp =
query("SELECT count(*) FROM analytics WHERE time > now() - interval '5 minutes'")
.fetch_one(&pool)
.await;
if ping != "PONG" {
return (
StatusCode::SERVICE_UNAVAILABLE,
Json(json!({
"status": "error",
"message": "Redis is not responding",
})),
);
match events_resp {
Ok(row) => {
let events_row: i64 = row.get(0);
if events_row == 0 {
return (
StatusCode::SERVICE_UNAVAILABLE,
Json(json!({
"websocket": "down",
"database": "up"
})),
);
} else {
return (
StatusCode::OK,
Json(json!({
"websocket": "up",
"database": "up"
})),
);
}
}
Err(_) => {
return (
StatusCode::SERVICE_UNAVAILABLE,
Json(json!({
"websocket": "down",
"database": "down"
})),
);
}
}
(
StatusCode::OK,
Json(json!({
"status": if ping == "PONG" && pc && ps4us && ps4eu { "ok" } else { "degraded" },
"redis": ping == "PONG",
"pc": if pc { "primary" } else { "backup/down" },
"ps4us": if ps4us { "primary" } else { "backup/down" },
"ps4eu": if ps4eu { "primary" } else { "backup/down" },
})),
)
}
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
@ -45,70 +51,43 @@ enum UpDown {
Down,
}
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
enum WebsocketState {
/// The Nanite Systems manifold is sending events, and the primary listener is processing data.
Primary,
/// The Daybreak Games manifold is sending events, and the backup listener is processing data; the primary listener is down.
Backup,
/// The entire event streaming system is down.
Down,
}
pub struct Health {}
impl Health {
async fn get_health<'ctx>(&self, ctx: &Context<'ctx>, pair: &str) -> WebsocketState {
let mut con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
let (primary, backup): (Option<String>, Option<String>) = pipe()
.get(format!("heartbeat:{}:primary", pair))
.get(format!("heartbeat:{}:backup", pair))
.query_async(&mut con)
.await
.unwrap();
match (primary, backup) {
(Some(_), _) => WebsocketState::Primary,
(None, Some(_)) => WebsocketState::Backup,
_ => WebsocketState::Down,
}
}
}
/// Reports on the health of Saerro Listening Post
#[Object]
impl Health {
/// Did a ping to Redis (our main datastore) succeed?
async fn redis<'ctx>(&self, ctx: &Context<'ctx>) -> UpDown {
let mut con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
let ping: String = redis::cmd("PING")
.query_async(&mut con)
.await
.unwrap_or_default();
if ping == "PONG" {
UpDown::Up
} else {
UpDown::Down
/// Did a ping to Postgres (our main datastore) succeed?
async fn database<'ctx>(&self, ctx: &Context<'ctx>) -> UpDown {
let pool = ctx.data::<Pool<Postgres>>().unwrap();
let events_resp =
query("SELECT count(*) FROM analytics WHERE time > now() - interval '5 minutes'")
.fetch_one(pool)
.await;
match events_resp {
Ok(_) => UpDown::Up,
Err(_) => UpDown::Down,
}
}
/// What is the state of the websocket listener cluster for PC?
#[graphql(name = "pc")]
async fn pc<'ctx>(&self, ctx: &Context<'ctx>) -> WebsocketState {
self.get_health(ctx, "pc").await
}
/// Is the websocket connection to the Nanite Systems manifold up?
async fn websocket<'ctx>(&self, ctx: &Context<'ctx>) -> UpDown {
let pool = ctx.data::<Pool<Postgres>>().unwrap();
/// What is the state of the websocket listener cluster for PS4 US?
#[graphql(name = "ps4us")]
async fn ps4us<'ctx>(&self, ctx: &Context<'ctx>) -> WebsocketState {
self.get_health(ctx, "ps4us").await
}
/// What is the state of the websocket listener cluster for PS4 EU?
#[graphql(name = "ps4eu")]
async fn ps4eu<'ctx>(&self, ctx: &Context<'ctx>) -> WebsocketState {
self.get_health(ctx, "ps4eu").await
match query("SELECT count(*) FROM analytics WHERE time > now() - interval '5 minutes'")
.fetch_one(pool)
.await
{
Ok(i) => {
let num: i64 = i.get(0);
if num == 0 {
UpDown::Down
} else {
UpDown::Up
}
}
Err(_) => UpDown::Down,
}
}
}

View file

@ -1,7 +1,6 @@
mod classes;
mod health;
mod query;
mod util;
mod vehicles;
mod world;
@ -58,20 +57,12 @@ async fn graphiql() -> impl IntoResponse {
#[tokio::main]
async fn main() {
let redis_url = format!(
"redis://{}:{}",
std::env::var("REDIS_HOST").unwrap_or("localhost".to_string()),
std::env::var("REDIS_PORT").unwrap_or("6379".to_string()),
);
let redis = redis::Client::open(redis_url)
.unwrap()
.get_multiplexed_tokio_connection()
.await
.unwrap();
let db_url = std::env::var("DATABASE_URL")
.unwrap_or("postgres://saerrouser:saerro321@localhost:5432/data".to_string());
let db = sqlx::PgPool::connect(&db_url).await.unwrap();
let schema = Schema::build(query::Query, EmptyMutation, EmptySubscription)
.data(redis.clone())
.data(db.clone())
.finish();
let app = Router::new()
@ -83,7 +74,7 @@ async fn main() {
)
.route("/graphql/playground", get(graphiql))
.fallback(handle_404)
.layer(Extension(redis))
.layer(Extension(db))
.layer(Extension(schema))
.layer(
CorsLayer::new()

View file

@ -1,17 +0,0 @@
use redis::{aio::MultiplexedConnection, AsyncCommands, FromRedisValue};
use std::{
ops::Sub,
time::{Duration, SystemTime},
};
pub async fn zcount<RV: FromRedisValue>(mut con: MultiplexedConnection, key: String) -> RV {
let filter_timestamp = SystemTime::now()
.sub(Duration::from_secs(60 * 15))
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
con.zcount::<String, u64, &'static str, RV>(key, filter_timestamp, "+inf")
.await
.unwrap()
}

View file

@ -1,6 +1,4 @@
use crate::util::zcount;
use async_graphql::{Context, Object};
use redis::aio::MultiplexedConnection;
pub struct Vehicles {
world_id: String,
@ -11,8 +9,7 @@ impl Vehicles {
Self { world_id }
}
async fn by_vehicle<'ctx>(&self, ctx: &Context<'ctx>, vehicle_name: &str) -> u32 {
let con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
zcount(con, format!("v:{}/{}", self.world_id, vehicle_name)).await
0
}
}

View file

@ -1,7 +1,6 @@
use crate::{classes::Classes, util::zcount, vehicles::Vehicles};
use crate::{classes::Classes, vehicles::Vehicles};
use async_graphql::{Context, Object};
use lazy_static::lazy_static;
use redis::aio::MultiplexedConnection;
use std::collections::HashMap;
lazy_static! {
@ -67,8 +66,7 @@ impl World {
}
async fn population<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
let con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
zcount(con, format!("wp:{}", self.id)).await
0
}
async fn faction_population(&self) -> FactionPopulation {
@ -92,8 +90,7 @@ struct FactionPopulation {
impl FactionPopulation {
async fn by_faction<'ctx>(&self, ctx: &Context<'ctx>, faction: u8) -> u32 {
let con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
zcount(con, format!("wp:{}/{}", self.world_id, faction)).await
0
}
}