websocket done, api needs rebuild
This commit is contained in:
parent
5c3a9a1888
commit
50c4ac387a
13 changed files with 192 additions and 195 deletions
|
@ -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"
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue