slot registration API skeleton
This commit is contained in:
parent
60ee718661
commit
93d8da878d
8 changed files with 183 additions and 28 deletions
|
|
@ -1,5 +1,4 @@
|
|||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
|
|
@ -32,13 +31,12 @@ fn main() {
|
|||
"unknown".to_string()
|
||||
};
|
||||
|
||||
|
||||
// Write a Rust file with the version constant
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
fs::write(
|
||||
format!("{}/global_config.rs", out_dir),
|
||||
format!("{}/version_info.rs", out_dir),
|
||||
format!("pub const GLOBAL_CONFIG_VERSION: &str = \"{}\";\n\
|
||||
pub const GLOBAL_CONFIG_BRANCH: &str = \"{}\";",
|
||||
version, branch),
|
||||
).expect("Failed to write global_config.rs");
|
||||
version, branch),
|
||||
).expect("Failed to write version_info.rs");
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/version_info.rs"));
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SsloAppState<T> {
|
||||
database_path: PathBuf,
|
||||
|
|
@ -29,4 +31,17 @@ where T: Clone
|
|||
pub fn substate_mut(&mut self) -> &mut T {
|
||||
&mut self.sub_state
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SsloAppStateRevisionInfo {
|
||||
/// The latest version number from a git tag
|
||||
fn version(&self) -> &str;
|
||||
|
||||
/// The name of the git repository branch during compile
|
||||
fn branch(&self) -> &str;
|
||||
}
|
||||
|
||||
impl<T> SsloAppStateRevisionInfo for SsloAppState<T> {
|
||||
fn version(&self) -> &str { GLOBAL_CONFIG_VERSION }
|
||||
fn branch(&self) -> &str { GLOBAL_CONFIG_BRANCH }
|
||||
}
|
||||
|
|
@ -1,5 +1,16 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use crate::app_state::SsloAppStateRevisionInfo;
|
||||
|
||||
/// This is the major version of the REST API
|
||||
/// With any compatibility breaking updates, this needs to be incremented
|
||||
const SLOT_REST_API_VERSION : u16 = 1;
|
||||
|
||||
pub enum ApiCompatibility {
|
||||
SlotOutdated,
|
||||
LeagueOutdated,
|
||||
Compatible,
|
||||
}
|
||||
|
||||
/// General slot information
|
||||
#[derive(ToSchema, Serialize, Deserialize)]
|
||||
|
|
@ -8,38 +19,86 @@ pub struct SlotInfoResponse {
|
|||
/// The name of the slot
|
||||
pub name: String,
|
||||
|
||||
/// The version of the slot
|
||||
/// The version identifier of the slot
|
||||
pub version: String,
|
||||
|
||||
/// The API version that is expected by the slot
|
||||
pub api_version: u16,
|
||||
|
||||
/// The repository branch that was used for compilation
|
||||
pub branch: String,
|
||||
}
|
||||
impl SlotInfoResponse{
|
||||
pub fn new(app_state: &impl SsloAppStateRevisionInfo, name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
version: app_state.version().to_string(),
|
||||
api_version: SLOT_REST_API_VERSION,
|
||||
branch: app_state.branch().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Request of a league to register on a slot
|
||||
#[derive(ToSchema, Serialize, Deserialize)]
|
||||
pub struct SlotRegisterRequest {
|
||||
|
||||
/// Full qualified domain name of the league, including port (e.g. sslo.my-league.org:443)
|
||||
/// Full qualified domain name of the league,
|
||||
/// including UDP port (e.g. sslo.my-league.org:8000)
|
||||
pub fqdn: String,
|
||||
|
||||
/// The version number of the league
|
||||
/// This is the requested ID of the slot in the league,
|
||||
/// It will be sent with all UDP requests from the slot to the league
|
||||
pub slot_id: u16,
|
||||
|
||||
/// The version identifier of the league
|
||||
pub version: String,
|
||||
|
||||
/// The API version that is expected by the league
|
||||
pub api_version: u16,
|
||||
}
|
||||
impl SlotRegisterRequest {
|
||||
pub fn new(app_state: &impl SsloAppStateRevisionInfo,
|
||||
fqdn: String,
|
||||
slot_id: u16,
|
||||
) -> Self { Self {
|
||||
fqdn,
|
||||
slot_id,
|
||||
version: app_state.version().to_string(),
|
||||
api_version: SLOT_REST_API_VERSION,
|
||||
} }
|
||||
|
||||
pub fn compatibility(&self) -> ApiCompatibility {
|
||||
if self.api_version < SLOT_REST_API_VERSION { ApiCompatibility::LeagueOutdated }
|
||||
else if self.api_version > SLOT_REST_API_VERSION { ApiCompatibility::SlotOutdated }
|
||||
else { ApiCompatibility::Compatible }
|
||||
}
|
||||
}
|
||||
|
||||
/// Request of a league to register on a slot
|
||||
#[derive(ToSchema, Serialize, Deserialize)]
|
||||
pub struct SlotRegisterResponse {
|
||||
|
||||
/// The Name of the slot
|
||||
/// The name of the slot
|
||||
pub name: String,
|
||||
|
||||
/// The version of the slot
|
||||
/// The version identifier of the slot
|
||||
pub version: String,
|
||||
|
||||
/// The API version that is expected by the slot
|
||||
pub api_version: u16,
|
||||
}
|
||||
impl SlotRegisterResponse {
|
||||
pub fn new(app_state: &impl SsloAppStateRevisionInfo, name: String) -> Self { Self {
|
||||
name,
|
||||
version: app_state.version().to_string(),
|
||||
api_version: SLOT_REST_API_VERSION,
|
||||
} }
|
||||
|
||||
|
||||
/// Continuous status reporting from slot to registered league
|
||||
#[derive(ToSchema, Serialize)]
|
||||
pub struct LeagueRequestSlotStatus {
|
||||
pub fn compatibility(&self) -> ApiCompatibility {
|
||||
if self.api_version < SLOT_REST_API_VERSION { ApiCompatibility::SlotOutdated }
|
||||
else if self.api_version > SLOT_REST_API_VERSION { ApiCompatibility::LeagueOutdated }
|
||||
else { ApiCompatibility::Compatible }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use axum_server::tls_rustls::RustlsConfig;
|
||||
use sslo_lib::app_state::SsloAppState;
|
||||
use sslo_lib::error::SsloError;
|
||||
|
|
@ -13,15 +14,16 @@ pub type AppState = SsloAppState<SlotAppState>;
|
|||
pub struct SlotAppState {
|
||||
pub shutdown_control: ShutdownControl,
|
||||
pub bearer_token_encrypted: String,
|
||||
pub config: UserConfig,
|
||||
pub user_config: UserConfig,
|
||||
pub database_dir: PathBuf,
|
||||
registered_league: Arc<RwLock<Option<(String, u16)>>>, // (fqdn, id)
|
||||
}
|
||||
|
||||
|
||||
impl SlotAppState {
|
||||
pub async fn new(database_dir: PathBuf) -> Result<AppState, SsloError> {
|
||||
|
||||
// parse config
|
||||
// parse user config
|
||||
let config = UserConfig::load_or_create(database_dir.join("config.toml"))?;
|
||||
|
||||
// shutdown controller
|
||||
|
|
@ -40,8 +42,9 @@ impl SlotAppState {
|
|||
let app_state = Self {
|
||||
shutdown_control,
|
||||
bearer_token_encrypted,
|
||||
config,
|
||||
user_config: config,
|
||||
database_dir: database_dir.clone(),
|
||||
registered_league: Arc::new(RwLock::new(None)),
|
||||
};
|
||||
Ok(SsloAppState::new(database_dir, app_state))
|
||||
}
|
||||
|
|
@ -78,4 +81,18 @@ impl SlotAppState {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_league(&self, fqdn: String, slot_id: u16) {
|
||||
let mut guard = self.registered_league.write().unwrap_or_else(|poisoned_guard| {
|
||||
poisoned_guard.into_inner()
|
||||
});
|
||||
*guard = Some((fqdn, slot_id));
|
||||
}
|
||||
|
||||
pub fn unregister_league(&self) {
|
||||
let mut guard = self.registered_league.write().unwrap_or_else(|poisoned_guard| {
|
||||
poisoned_guard.into_inner()
|
||||
});
|
||||
*guard = None;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
mod info;
|
||||
mod register;
|
||||
|
||||
use crate::app_state::AppState;
|
||||
|
||||
|
|
@ -21,6 +22,8 @@ use sslo_lib::token::Token;
|
|||
pub fn create_router(app_state: AppState) -> Router {
|
||||
let (router, api) = OpenApiRouter::<AppState>::with_openapi(ApiDoc::openapi())
|
||||
.routes(routes!(info::handler))
|
||||
.routes(routes!(register::handler_post))
|
||||
.routes(routes!(register::handler_delete))
|
||||
.split_for_parts();
|
||||
|
||||
// bearer authorization middleware
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ use axum::response::{IntoResponse, Response};
|
|||
use sslo_lib::slot_rest_api::SlotInfoResponse;
|
||||
use crate::app_state::AppState;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/global_config.rs"));
|
||||
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
|
|
@ -19,12 +17,7 @@ include!(concat!(env!("OUT_DIR"), "/global_config.rs"));
|
|||
)
|
||||
)]
|
||||
pub async fn handler(State(app_state): State<AppState>) -> Response {
|
||||
|
||||
let res = SlotInfoResponse {
|
||||
name: app_state.substate().config.name.clone(),
|
||||
version: GLOBAL_CONFIG_VERSION.to_string(),
|
||||
branch: GLOBAL_CONFIG_BRANCH.to_string(),
|
||||
};
|
||||
|
||||
let name = app_state.substate().user_config.name.clone();
|
||||
let res = SlotInfoResponse::new(&app_state, name);
|
||||
(StatusCode::OK, Json(res)).into_response()
|
||||
}
|
||||
|
|
|
|||
70
sslo_slot/src/http/register.rs
Normal file
70
sslo_slot/src/http/register.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::Json;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use sslo_lib::app_state::SsloAppStateRevisionInfo;
|
||||
use sslo_lib::slot_rest_api::{ApiCompatibility, SlotRegisterRequest, SlotRegisterResponse};
|
||||
use crate::app_state::AppState;
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag="League Interface",
|
||||
path="/register",
|
||||
summary="register a league",
|
||||
description="Registers a league to this slot. An already existing league registration will be dropped.",
|
||||
request_body=SlotRegisterRequest,
|
||||
responses(
|
||||
(status=200, body=SlotRegisterResponse),
|
||||
(status=406, body=SlotRegisterResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn handler_post(State(app_state): State<AppState>,
|
||||
Json(request): Json<SlotRegisterRequest>,
|
||||
) -> Response {
|
||||
|
||||
// prepare response
|
||||
let name = app_state.substate().user_config.name.clone();
|
||||
let res = SlotRegisterResponse::new(&app_state, name);
|
||||
|
||||
// check compatibility
|
||||
let status = match request.compatibility() {
|
||||
ApiCompatibility::SlotOutdated => {
|
||||
log::warn!("Reject league registration request, because slot is outdated! League version={} slot version={}",
|
||||
request.version, app_state.version(),
|
||||
);
|
||||
StatusCode::NOT_ACCEPTABLE
|
||||
},
|
||||
ApiCompatibility::LeagueOutdated => {
|
||||
log::warn!("Reject league registration request, because league is outdated! League version={} slot version={}",
|
||||
request.version, app_state.version(),
|
||||
);
|
||||
StatusCode::NOT_ACCEPTABLE
|
||||
},
|
||||
ApiCompatibility::Compatible => {
|
||||
log::info!("League registration request accepted. League '{}' with version={}",
|
||||
request.fqdn, request.version,
|
||||
);
|
||||
app_state.substate().register_league(request.fqdn, request.slot_id);
|
||||
StatusCode::OK
|
||||
},
|
||||
};
|
||||
|
||||
// send response
|
||||
(status, Json(res)).into_response()
|
||||
}
|
||||
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
tag="League Interface",
|
||||
path="/register",
|
||||
summary="unregister any league",
|
||||
description="Unregister the current league from this slot. This has no effect when no league is connected.",
|
||||
responses(
|
||||
(status=204),
|
||||
)
|
||||
)]
|
||||
pub async fn handler_delete(State(app_state): State<AppState>) -> Response {
|
||||
app_state.substate().unregister_league();
|
||||
StatusCode::NO_CONTENT.into_response()
|
||||
}
|
||||
|
|
@ -35,8 +35,8 @@ async fn main() {
|
|||
let axum_app = http::create_router(app_state.clone());
|
||||
let axum_handle = axum_server::Handle::new();
|
||||
app_state.substate_mut().shutdown_control.add_axum_server_handle(axum_handle.clone());
|
||||
let socket_addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, app_state.substate().config.start_port));
|
||||
if app_state.substate().config.use_embedded_tls {
|
||||
let socket_addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, app_state.substate().user_config.start_port));
|
||||
if app_state.substate().user_config.use_embedded_tls {
|
||||
if let Ok(tls_cfg) = app_state.substate().get_rustls_config().await {
|
||||
match axum_server::bind_rustls(socket_addr, tls_cfg)
|
||||
.handle(axum_handle)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue