Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

editoast: refactor layers get records and app health #7939

Merged
merged 2 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions editoast/editoast_models/src/db_connection_pool.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
use std::sync::Arc;

use diesel::sql_query;
use diesel::ConnectionError;
use diesel::ConnectionResult;
use diesel_async::pooled_connection::deadpool::Object;
use diesel_async::pooled_connection::deadpool::Pool;

use diesel_async::pooled_connection::AsyncDieselConnectionManager;
use diesel_async::pooled_connection::ManagerConfig;
use diesel_async::AsyncPgConnection;
use diesel_async::RunQueryDsl;
use futures::future::BoxFuture;
use futures::Future;
use futures_util::FutureExt as _;
use openssl::ssl::SslConnector;
use openssl::ssl::SslMethod;
use openssl::ssl::SslVerifyMode;
use std::sync::Arc;
use url::Url;

#[cfg(feature = "testing")]
Expand Down Expand Up @@ -328,6 +330,11 @@ impl DbConnectionPoolV2 {
}
}

pub async fn ping_database(conn: &mut DbConnection) -> Result<(), EditoastModelsError> {
sql_query("SELECT 1").execute(conn).await?;
Ok(())
}

pub fn create_connection_pool(
url: Url,
max_size: usize,
Expand Down
1 change: 1 addition & 0 deletions editoast/editoast_models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod db_connection_pool;
mod error;

pub use db_connection_pool::create_connection_pool;
pub use db_connection_pool::ping_database;
pub use db_connection_pool::DbConnectionPoolV2;
pub use error::EditoastModelsError;

Expand Down
60 changes: 60 additions & 0 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3794,6 +3794,63 @@ components:
enum:
- STANDARD
- MARECO
EditoastAppHealthErrorDatabase:
type: object
required:
- type
- status
- message
properties:
context:
type: object
message:
type: string
status:
type: integer
enum:
- 400
type:
type: string
enum:
- editoast:app_health:Database
EditoastAppHealthErrorRedis:
type: object
required:
- type
- status
- message
properties:
context:
type: object
message:
type: string
status:
type: integer
enum:
- 400
type:
type: string
enum:
- editoast:app_health:Redis
EditoastAppHealthErrorTimeout:
type: object
required:
- type
- status
- message
properties:
context:
type: object
message:
type: string
status:
type: integer
enum:
- 400
type:
type: string
enum:
- editoast:app_health:Timeout
EditoastAttachedErrorTrackNotFound:
type: object
required:
Expand Down Expand Up @@ -4248,6 +4305,9 @@ components:
- editoast:electrical_profiles:NotFound
EditoastError:
oneOf:
- $ref: '#/components/schemas/EditoastAppHealthErrorDatabase'
- $ref: '#/components/schemas/EditoastAppHealthErrorRedis'
- $ref: '#/components/schemas/EditoastAppHealthErrorTimeout'
- $ref: '#/components/schemas/EditoastAttachedErrorTrackNotFound'
- $ref: '#/components/schemas/EditoastAutoFixesEditoastErrorConflictingFixesOnSameObject'
- $ref: '#/components/schemas/EditoastAutoFixesEditoastErrorFixTrialFailure'
Expand Down
1 change: 1 addition & 0 deletions editoast/src/modelsv2/layers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod geo_json_and_data;
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use diesel::sql_query;
use diesel::sql_types::Integer;
use diesel::sql_types::Jsonb;
use diesel::sql_types::Text;
use diesel_async::RunQueryDsl;
use editoast_models::DbConnection;
use editoast_models::EditoastModelsError;
use geos::geojson::Geometry;
use geos::geojson::Value as GeoJsonValue;
use mvt::Feature;
Expand All @@ -11,6 +16,7 @@ use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;

use crate::map::Layer;
use crate::map::View;

#[derive(Clone, QueryableByName, Queryable, Debug, Serialize, Deserialize)]
Expand All @@ -21,17 +27,39 @@ pub struct GeoJsonAndData {
pub data: JsonValue,
}

fn geometry_into_mvt_geom_type(geometry: &Geometry) -> GeomType {
match geometry.value {
GeoJsonValue::Point { .. } => GeomType::Point,
GeoJsonValue::MultiPoint { .. } => GeomType::Point,
GeoJsonValue::LineString { .. } => GeomType::Linestring,
GeoJsonValue::MultiLineString { .. } => GeomType::Linestring,
_ => panic!("geometry type unsupported by editoast tiling system"),
#[derive(Debug)]
pub struct GeoPoint {
x: u64,
y: u64,
z: u64,
}

impl GeoPoint {
pub fn new(x: u64, y: u64, z: u64) -> Self {
Self { x, y, z }
}
}

impl GeoJsonAndData {
pub async fn get_records(
conn: &mut DbConnection,
layer: &Layer,
view: &View,
infra: i64,
geo_point: &GeoPoint,
) -> Result<Vec<GeoJsonAndData>, EditoastModelsError> {
let geo_json_query = get_geo_json_sql_query(&layer.table_name, view);
let records = sql_query(geo_json_query)
.bind::<Integer, _>(geo_point.z as i32)
.bind::<Integer, _>(geo_point.x as i32)
.bind::<Integer, _>(geo_point.y as i32)
.bind::<Integer, _>(infra as i32)
.get_results::<GeoJsonAndData>(conn)
.await?;

Ok(records)
}

/// Converts GeoJsonAndData as mvt GeomData
pub fn as_geom_data(&self) -> GeomData {
let geo_json = serde_json::from_str::<Geometry>(&self.geo_json).unwrap();
Expand Down Expand Up @@ -65,6 +93,16 @@ impl GeoJsonAndData {
}
}

fn geometry_into_mvt_geom_type(geometry: &Geometry) -> GeomType {
match geometry.value {
GeoJsonValue::Point { .. } => GeomType::Point,
GeoJsonValue::MultiPoint { .. } => GeomType::Point,
GeoJsonValue::LineString { .. } => GeomType::Linestring,
GeoJsonValue::MultiLineString { .. } => GeomType::Linestring,
_ => panic!("geometry type unsupported by editoast tiling system"),
}
}

/// Adds tags to an MVT feature
///
/// tags must be flattened as mvt tags are only one level depth
Expand Down
1 change: 1 addition & 0 deletions editoast/src/modelsv2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod electrical_profiles;
pub mod fixtures;
pub mod infra;
pub mod infra_objects;
pub mod layers;
pub mod light_rolling_stock;
// We allow unused until models is moved to a separate crate
pub mod pagination;
Expand Down
18 changes: 14 additions & 4 deletions editoast/src/redis_utils.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::client::RedisConfig;
use crate::error::Result;
use std::fmt::Debug;

use futures::future;
use futures::FutureExt;
use redis::aio::{ConnectionLike, ConnectionManager};
use redis::aio::ConnectionLike;
use redis::aio::ConnectionManager;
use redis::cluster::ClusterClient;
use redis::cluster_async::ClusterConnection;
use redis::cmd;
use redis::AsyncCommands;
use redis::Client;
use redis::ErrorKind;
Expand All @@ -14,7 +16,9 @@ use redis::RedisResult;
use redis::ToRedisArgs;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fmt::Debug;

use crate::client::RedisConfig;
use crate::error::Result;

pub enum RedisConnection {
Cluster(ClusterConnection),
Expand Down Expand Up @@ -194,4 +198,10 @@ impl RedisClient {
RedisClient::NoCache => Ok(RedisConnection::NoCache),
}
}

pub async fn ping_redis(&self) -> RedisResult<()> {
let mut conn = self.get_connection().await?;
cmd("PING").query_async::<_, ()>(&mut conn).await?;
Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
mod mvt_utils;

use std::collections::HashMap;

use actix_web::get;
Expand All @@ -8,13 +6,8 @@ use actix_web::web::Json;
use actix_web::web::Path;
use actix_web::web::Query;
use actix_web::HttpResponse;
use diesel::sql_query;
use diesel::sql_types::Integer;
use diesel_async::RunQueryDsl;
use editoast_derive::EditoastError;
use mvt_utils::create_and_fill_mvt_tile;
use mvt_utils::get_geo_json_sql_query;
use mvt_utils::GeoJsonAndData;
use editoast_models::DbConnectionPoolV2;
use redis::AsyncCommands;
use serde::Deserialize;
use serde::Serialize;
Expand All @@ -30,8 +23,10 @@ use crate::map::get_view_cache_prefix;
use crate::map::Layer;
use crate::map::MapLayers;
use crate::map::Tile;
use crate::modelsv2::layers::geo_json_and_data::create_and_fill_mvt_tile;
use crate::modelsv2::layers::geo_json_and_data::GeoJsonAndData;
use crate::modelsv2::layers::geo_json_and_data::GeoPoint;
use crate::RedisClient;
use editoast_models::DbConnectionPoolV2;

crate::routes! {
"/layers" => {
Expand Down Expand Up @@ -206,15 +201,9 @@ async fn cache_and_get_mvt_tile(
.body(value));
}

let geo_json_query = get_geo_json_sql_query(&layer.table_name, view);
let mut conn = db_pool.get().await?;
let records = sql_query(geo_json_query)
.bind::<Integer, _>(z as i32)
.bind::<Integer, _>(x as i32)
.bind::<Integer, _>(y as i32)
.bind::<Integer, _>(infra as i32)
.get_results::<GeoJsonAndData>(&mut conn)
.await?;
let conn = &mut db_pool.get().await?;
let records =
GeoJsonAndData::get_records(conn, layer, view, infra, &GeoPoint::new(z, x, y)).await?;

let mvt_bytes: Vec<u8> = create_and_fill_mvt_tile(layer_slug, records)
.to_bytes()
Expand Down
47 changes: 37 additions & 10 deletions editoast/src/views/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,22 @@ pub mod work_schedules;
mod test_app;

use std::ops::DerefMut as _;
use std::sync::Arc;
use std::time::Duration;

use futures::TryFutureExt;
pub use openapi::OpenApiRoot;

use actix_web::get;
use actix_web::web::Data;
use actix_web::web::Json;
use diesel::sql_query;
use redis::cmd;
use editoast_derive::EditoastError;
use editoast_models::ping_database;
use editoast_models::DbConnectionPoolV2;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use thiserror::Error;
use tokio::time::timeout;
use utoipa::ToSchema;

use crate::client::get_app_version;
Expand All @@ -49,7 +55,6 @@ use crate::infra_cache::operation;
use crate::models;
use crate::modelsv2;
use crate::RedisClient;
use editoast_models::DbConnectionPoolV2;

crate::routes! {
(health, version, core_version),
Expand Down Expand Up @@ -95,6 +100,17 @@ editoast_common::schemas! {
work_schedules::schemas(),
}

#[derive(Debug, Error, EditoastError)]
#[editoast_error(base_id = "app_health")]
pub enum AppHealthError {
#[error("Timeout error")]
Timeout,
#[error(transparent)]
Database(#[from] editoast_models::EditoastModelsError),
#[error(transparent)]
Redis(#[from] redis::RedisError),
}

#[utoipa::path(
responses(
(status = 200, description = "Check if Editoast is running correctly", body = String)
Expand All @@ -105,16 +121,27 @@ async fn health(
db_pool: Data<DbConnectionPoolV2>,
redis_client: Data<RedisClient>,
) -> Result<&'static str> {
use diesel_async::RunQueryDsl;
sql_query("SELECT 1")
.execute(db_pool.get().await?.deref_mut())
.await?;

let mut conn = redis_client.get_connection().await?;
cmd("PING").query_async::<_, ()>(&mut conn).await.unwrap();
timeout(
Duration::from_millis(500),
check_health(db_pool.into_inner(), redis_client.into_inner()),
)
.await
.map_err(|_| AppHealthError::Timeout)??;
Ok("ok")
}

async fn check_health(
db_pool: Arc<DbConnectionPoolV2>,
redis_client: Arc<RedisClient>,
) -> Result<()> {
let mut db_connection = db_pool.clone().get().await?;
tokio::try_join!(
ping_database(db_connection.deref_mut()).map_err(AppHealthError::Database),
redis_client.ping_redis().map_err(|e| e.into())
)?;
Ok(())
}

#[derive(ToSchema, Serialize, Deserialize)]
pub struct Version {
#[schema(required)] // Options are by default not required, but this one is
Expand Down
5 changes: 5 additions & 0 deletions front/public/locales/en/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@
},
"work_schedule": {
"NameAlreadyUsed": "A group of work schedules with '{{name}}' already exists"
},
"app_health": {
"Timeout": "Service has not responded in time",
"Database": "Database is in error",
"Redis": "Redis is in error"
}
}
}
Loading
Loading