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

Isolate runserver CLI in client module and move AppState into views #9605

Merged
merged 9 commits into from
Nov 14, 2024
17 changes: 8 additions & 9 deletions editoast/editoast_osrdyne_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::HashMap;

use itertools::Itertools;
use serde::Deserialize;
use url::Url;

#[cfg(any(test, feature = "mock_client"))]
mod mock_client;
Expand All @@ -22,15 +23,13 @@ struct HTTPClient {
}

impl OsrdyneClient {
pub fn new(osrdyne_url: &str) -> Result<Self, url::ParseError> {
let client = HTTPClient {
client: reqwest::Client::new(),
base_url: url::Url::parse(osrdyne_url)?,
};
let client = OsrdyneClient {
inner: OsrdyneClientInternal::HTTPClient(client),
};
Ok(client)
pub fn new(osrdyne_url: Url) -> Self {
OsrdyneClient {
inner: OsrdyneClientInternal::HTTPClient(HTTPClient {
client: reqwest::Client::new(),
base_url: osrdyne_url,
}),
}
}

#[cfg(any(test, feature = "mock_client"))]
Expand Down
25 changes: 0 additions & 25 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4287,7 +4287,6 @@ components:
- $ref: '#/components/schemas/EditoastTrainScheduleErrorBatchTrainScheduleNotFound'
- $ref: '#/components/schemas/EditoastTrainScheduleErrorInfraNotFound'
- $ref: '#/components/schemas/EditoastTrainScheduleErrorNotFound'
- $ref: '#/components/schemas/EditoastValkeyConfigErrorUrl'
- $ref: '#/components/schemas/EditoastWorkScheduleErrorNameAlreadyUsed'
description: Generated error type for Editoast
discriminator:
Expand Down Expand Up @@ -5683,30 +5682,6 @@ components:
type: string
enum:
- editoast:train_schedule:NotFound
EditoastValkeyConfigErrorUrl:
type: object
required:
- type
- status
- message
properties:
context:
type: object
required:
- url
properties:
url:
type: string
message:
type: string
status:
type: integer
enum:
- 500
type:
type: string
enum:
- editoast:valkey:Url
EditoastWorkScheduleErrorNameAlreadyUsed:
type: object
required:
Expand Down
33 changes: 33 additions & 0 deletions editoast/src/client/healthcheck.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::sync::Arc;

use anyhow::anyhow;
use editoast_models::DbConnectionPoolV2;

use crate::{
core::{mq_client, CoreClient},
views::check_health,
ValkeyClient,
};

use super::{runserver::CoreArgs, ValkeyConfig};

pub async fn healthcheck_cmd(
db_pool: Arc<DbConnectionPoolV2>,
valkey_config: ValkeyConfig,
core_config: CoreArgs,
) -> anyhow::Result<()> {
let valkey = ValkeyClient::new(valkey_config.into()).unwrap();
let core_client = CoreClient::new_mq(mq_client::Options {
uri: core_config.mq_url,
worker_pool_identifier: String::from("core"),
timeout: core_config.core_timeout,
single_worker: core_config.core_single_worker,
num_channels: core_config.core_client_channels_size,
})
.await?;
check_health(db_pool, valkey.into(), core_client.into())
.await
.map_err(|e| anyhow!("❌ healthcheck failed: {e}"))?;
println!("✅ Healthcheck passed");
Ok(())
}
4 changes: 2 additions & 2 deletions editoast/src/client/infra_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,11 @@ async fn build_valkey_pool_and_invalidate_all_cache(
valkey_config: ValkeyConfig,
infra_id: i64,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let valkey = ValkeyClient::new(valkey_config).unwrap();
let valkey = ValkeyClient::new(valkey_config.into()).unwrap();
let mut conn = valkey.get_connection().await.unwrap();
Ok(map::invalidate_all(
&mut conn,
&MapLayers::parse().layers.keys().cloned().collect(),
&MapLayers::default().layers.keys().cloned().collect(),
infra_id,
)
.await
Expand Down
65 changes: 11 additions & 54 deletions editoast/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub mod electrical_profiles_commands;
pub mod healthcheck;
pub mod import_rolling_stock;
pub mod infra_commands;
mod postgres_config;
pub mod roles;
pub mod runserver;
pub mod search_commands;
pub mod stdcm_search_env_commands;
mod telemetry_config;
Expand All @@ -22,6 +24,8 @@ use import_rolling_stock::ImportRollingStockArgs;
use infra_commands::InfraCommands;
pub use postgres_config::PostgresConfig;
use roles::RolesCommand;
use runserver::CoreArgs;
use runserver::RunserverArgs;
use search_commands::SearchCommands;
use stdcm_search_env_commands::StdcmSearchEnvCommands;
pub use telemetry_config::TelemetryConfig;
Expand All @@ -32,6 +36,7 @@ use url::Url;
pub use valkey_config::ValkeyConfig;

use crate::error::Result;
use crate::views::OpenApiRoot;

#[derive(Parser, Debug)]
#[command(author, version)]
Expand Down Expand Up @@ -89,60 +94,6 @@ pub enum Commands {
Healthcheck(CoreArgs),
}

#[derive(Args, Debug, Derivative, Clone)]
#[derivative(Default)]
pub struct MapLayersConfig {
#[derivative(Default(value = "18"))]
#[arg(long, env, default_value_t = 18)]
pub max_zoom: u64,
/// Number maximum of tiles before we consider invalidating full Valkey cache is required
#[derivative(Default(value = "250_000"))]
#[arg(long, env, default_value_t = 250_000)]
pub max_tiles: u64,
}

#[derive(Args, Debug)]
#[command(about, long_about = "Launch the server")]
pub struct CoreArgs {
#[clap(long, env = "OSRD_MQ_URL", default_value_t = String::from("amqp://osrd:[email protected]:5672/%2f"))]
pub mq_url: String,
#[clap(long, env = "EDITOAST_CORE_TIMEOUT", default_value_t = 180)]
pub core_timeout: u64,
#[clap(long, env = "EDITOAST_CORE_SINGLE_WORKER", default_value_t = false)]
pub core_single_worker: bool,
#[clap(long, env = "CORE_CLIENT_CHANNELS_SIZE", default_value_t = 8)]
pub core_client_channels_size: usize,
}

#[derive(Args, Debug)]
#[command(about, long_about = "Launch the server")]
pub struct RunserverArgs {
#[command(flatten)]
pub map_layers_config: MapLayersConfig,
#[arg(long, env = "EDITOAST_PORT", default_value_t = 8090)]
pub port: u16,
#[arg(long, env = "EDITOAST_ADDRESS", default_value_t = String::from("0.0.0.0"))]
pub address: String,
#[command(flatten)]
pub core: CoreArgs,
#[clap(long, env = "ROOT_PATH", default_value_t = String::new())]
pub root_path: String,
#[clap(long)]
pub workers: Option<usize>,
/// If this option is set, any role and permission check will be bypassed. Even if no user is
/// provided by the request headers of if the provided user doesn't have the required privileges.
// TODO: once the whole role system will be deployed, the default value of this option should
// be set to false. It's currently set to true in order to pass integration tests, which otherwise
// only recieve 401 responses.
#[clap(long, env = "EDITOAST_DISABLE_AUTHORIZATION", default_value_t = true)]
pub disable_authorization: bool,
#[clap(long, env = "OSRDYNE_API_URL", default_value_t = String::from("http://127.0.0.1:4242/"))]
pub osrdyne_api_url: String,
/// The timeout to use when performing the healthcheck, in milliseconds
#[clap(long, env = "EDITOAST_HEALTH_CHECK_TIMEOUT_MS", default_value_t = 500)]
pub health_check_timeout_ms: u64,
}

#[derive(Args, Debug)]
#[command(about, long_about = "Extracts a railjson from OpenStreetMap data")]
pub struct OsmToRailjsonArgs {
Expand All @@ -152,6 +103,12 @@ pub struct OsmToRailjsonArgs {
pub railjson_out: PathBuf,
}

/// Prints the OpenApi to stdout
pub fn print_openapi() {
let openapi = OpenApiRoot::build_openapi();
print!("{}", serde_yaml::to_string(&openapi).unwrap());
}

/// Retrieve the ROOT_URL env var. If not found returns default local url.
pub fn get_root_url() -> Result<Url> {
let url = env::var("ROOT_URL").unwrap_or(String::from("http://localhost:8090"));
Expand Down
16 changes: 12 additions & 4 deletions editoast/src/client/postgres_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use clap::Args;
use derivative::Derivative;
use url::Url;

use crate::error::Result;
use crate::views;

#[derive(Args, Debug, Derivative, Clone)]
#[derivative(Default)]
Expand All @@ -21,8 +21,16 @@ pub struct PostgresConfig {
pub pool_size: usize,
}

impl PostgresConfig {
pub fn url(&self) -> Result<Url> {
Ok(self.database_url.clone())
impl From<PostgresConfig> for views::PostgresConfig {
fn from(
PostgresConfig {
database_url,
pool_size,
}: PostgresConfig,
) -> Self {
views::PostgresConfig {
database_url,
pool_size,
}
}
}
94 changes: 94 additions & 0 deletions editoast/src/client/runserver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use chrono::Duration;
use clap::Args;
use url::Url;

use crate::views;

use super::{PostgresConfig, ValkeyConfig};

#[derive(Args, Debug, Clone)]
struct MapLayersConfig {
#[arg(long, env, default_value_t = 18)]
max_zoom: u64,
}

#[derive(Args, Debug)]
#[command(about, long_about = "Launch the server")]
pub struct CoreArgs {
#[clap(long, env = "OSRD_MQ_URL", default_value_t = Url::parse("amqp://osrd:[email protected]:5672/%2f").unwrap())]
pub(super) mq_url: Url,
#[clap(long, env = "EDITOAST_CORE_TIMEOUT", default_value_t = 180)]
pub(super) core_timeout: u64,
#[clap(long, env = "EDITOAST_CORE_SINGLE_WORKER", default_value_t = false)]
pub(super) core_single_worker: bool,
#[clap(long, env = "CORE_CLIENT_CHANNELS_SIZE", default_value_t = 8)]
pub(super) core_client_channels_size: usize,
}

#[derive(Args, Debug)]
#[command(about, long_about = "Launch the server")]
pub struct RunserverArgs {
#[command(flatten)]
map_layers_config: MapLayersConfig,
#[arg(long, env = "EDITOAST_PORT", default_value_t = 8090)]
port: u16,
#[arg(long, env = "EDITOAST_ADDRESS", default_value_t = String::from("0.0.0.0"))]
address: String,
#[command(flatten)]
core: CoreArgs,
/// If this option is set, any role and permission check will be bypassed. Even if no user is
/// provided by the request headers of if the provided user doesn't have the required privileges.
// TODO: once the whole role system will be deployed, the default value of this option should
// be set to false. It's currently set to true in order to pass integration tests, which otherwise
// only recieve 401 responses.
#[clap(long, env = "EDITOAST_DISABLE_AUTHORIZATION", default_value_t = true)]
disable_authorization: bool,
#[clap(long, env = "OSRDYNE_API_URL", default_value_t = Url::parse("http://127.0.0.1:4242/").unwrap())]
osrdyne_api_url: Url,
/// The timeout to use when performing the healthcheck, in milliseconds
#[clap(long, env = "EDITOAST_HEALTH_CHECK_TIMEOUT_MS", default_value_t = 500)]
health_check_timeout_ms: u64,
}

/// Create and run the server
pub async fn runserver(
RunserverArgs {
map_layers_config,
port,
address,
core:
CoreArgs {
mq_url,
core_timeout,
core_single_worker,
core_client_channels_size,
},
disable_authorization,
osrdyne_api_url,
health_check_timeout_ms,
}: RunserverArgs,
postgres: PostgresConfig,
valkey: ValkeyConfig,
) -> anyhow::Result<()> {
let config = views::ServerConfig {
port,
address,
health_check_timeout: Duration::milliseconds(health_check_timeout_ms as i64),
map_layers_max_zoom: map_layers_config.max_zoom as u8,
disable_authorization,
postgres_config: postgres.into(),
osrdyne_config: views::OsrdyneConfig {
mq_url,
osrdyne_api_url,
core: views::CoreConfig {
timeout: Duration::seconds(core_timeout as i64),
single_worker: core_single_worker,
num_channels: core_client_channels_size,
},
},
valkey_config: valkey.into(),
};

let server = views::Server::new(config).await?;
server.start().await.map_err(Into::into)
}
37 changes: 17 additions & 20 deletions editoast/src/client/valkey_config.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use clap::Args;
use derivative::Derivative;
use editoast_derive::EditoastError;
use thiserror::Error;
use url::Url;

use crate::error::Result;
use crate::valkey_utils;

#[derive(Args, Debug, Derivative, Clone)]
#[derivative(Default)]
Expand All @@ -16,25 +14,24 @@ pub struct ValkeyConfig {
#[derivative(Default(value = "false"))]
#[clap(long, env, default_value_t = false)]
pub is_cluster_client: bool,
#[derivative(Default(value = r#""redis://localhost:6379".into()"#))]
#[arg(long, env, default_value = "redis://localhost:6379")]
#[derivative(Default(value = r#"Url::parse("redis://localhost:6379").unwrap()"#))]
#[arg(long, env, default_value_t = Url::parse("redis://localhost:6379").unwrap())]
/// Valkey url like `redis://[:PASSWORD@]HOST[:PORT][/DATABASE]`
valkey_url: String,
pub valkey_url: Url,
}

impl ValkeyConfig {
pub fn url(&self) -> Result<Url> {
let url = Url::parse(&self.valkey_url).map_err(|_| ValkeyConfigError::Url {
url: self.valkey_url.clone(),
})?;

Ok(url)
impl From<ValkeyConfig> for valkey_utils::ValkeyConfig {
fn from(
ValkeyConfig {
no_cache,
is_cluster_client,
valkey_url,
}: ValkeyConfig,
) -> Self {
valkey_utils::ValkeyConfig {
no_cache,
is_cluster_client,
valkey_url,
}
}
}

#[derive(Debug, Error, EditoastError)]
#[editoast_error(base_id = "valkey", default_status = 500)]
pub enum ValkeyConfigError {
#[error("Invalid url '{url}'")]
Url { url: String },
}
Loading
Loading