Skip to content

Commit

Permalink
Use status from DB, add get status endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
evanjt committed Oct 28, 2024
1 parent 7e8cbdd commit 078110c
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 29 deletions.
6 changes: 6 additions & 0 deletions src/common/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ impl UIConfiguration {
pub struct HealthCheck {
pub status: String,
}

#[derive(ToSchema, Deserialize, Serialize)]
pub struct ServiceStatus {
pub s3_status: bool,
pub kubernetes_status: bool,
}
68 changes: 64 additions & 4 deletions src/common/views.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use super::models::HealthCheck;
use crate::common::models::UIConfiguration;
use super::models::{HealthCheck, ServiceStatus};
use crate::external::db;
use crate::external::db::ServiceName;
use crate::external::k8s::services::get_pods;
use crate::{common::models::UIConfiguration, external::s3};
use axum::{extract::State, http::StatusCode, Json};
use sea_orm::DatabaseConnection;
use sea_orm::{
ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter, QueryOrder, QuerySelect,
Set,
};
use std::sync::Arc;

#[utoipa::path(
get,
Expand Down Expand Up @@ -62,7 +68,61 @@ pub async fn healthz(State(db): State<DatabaseConnection>) -> (StatusCode, Json<
)
)
)]

pub async fn get_ui_config() -> Json<UIConfiguration> {
Json(UIConfiguration::new())
}

#[utoipa::path(
get,
path = "/api/status",
responses(
(
status = OK,
description = "Status of the API",
body = str,
content_type = "text/plain"
)
)
)]

pub async fn get_status(State(db): State<DatabaseConnection>) -> Json<ServiceStatus> {
let config = crate::config::Config::from_env();
// Check the status of kubernetes and S3 from the last DB entry.
// This assumes the background runner is updating at frequent intervals
let k8s = db::Entity::find()
.filter(db::Column::ServiceName.eq(ServiceName::RCP))
.order_by_desc(db::Column::TimeUtc)
.limit(1)
.all(&db)
.await
.unwrap();

let mut k8s_online = k8s.get(0).unwrap().is_online;

if (chrono::Utc::now().naive_utc() - k8s.get(0).unwrap().time_utc).num_seconds() as u64
> config.interval_external_services * 2
{
k8s_online = false;
}

let s3 = db::Entity::find()
.filter(db::Column::ServiceName.eq(ServiceName::S3))
.order_by_desc(db::Column::TimeUtc)
.limit(1)
.all(&db)
.await
.unwrap();

let mut s3_online = s3.get(0).unwrap().is_online;

if (chrono::Utc::now().naive_utc() - s3.get(0).unwrap().time_utc).num_seconds() as u64
> config.interval_external_services * 2
{
s3_online = false;
}

Json(ServiceStatus {
s3_status: s3_online,
kubernetes_status: k8s_online,
})
}
5 changes: 5 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct Config {
pub deployment: String,
pub _kube_config: PathBuf,
pub kube_namespace: String,
pub interval_external_services: u64,

pub s3_prefix: String, // Prefix within the bucket, ie. labcaller-dev
pub pod_prefix: String, // What is prefixed to the pod name, ie. labcaller-dev}
Expand Down Expand Up @@ -69,6 +70,10 @@ impl Config {
.expect("KUBECONFIG must be set")
.into(),
kube_namespace: env::var("KUBE_NAMESPACE").expect("KUBE_NAMESPACE must be set"),
interval_external_services: env::var("INTERVAL_EXTERNAL_SERVICES")
.unwrap_or_else(|_| "60".to_string())
.parse()
.unwrap(),
db_prefix,
db_url,
s3_prefix,
Expand Down
75 changes: 51 additions & 24 deletions src/external/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,71 @@ use super::db::{ActiveModel, Entity};
use super::models::ServiceCreate;
use crate::config::Config;
use crate::external::db::ServiceName;
use anyhow::{anyhow, Result};
use k8s_openapi::api::core::v1::Service;
use sea_orm::{Database, DatabaseConnection, EntityTrait};

async fn check_kubernetes() -> Result<serde_json::Value> {
let pods_result = crate::external::k8s::services::get_pods().await;

match pods_result {
Ok(Some(pods)) => Ok(serde_json::to_value(pods).unwrap()),
Ok(_) => Ok(serde_json::to_value("No pods found").unwrap()),
Err(err) => Err(anyhow!(serde_json::to_value(err.to_string()).unwrap())),
}
}

async fn check_s3(config: &Config) -> Result<serde_json::Value> {
let s3_client = crate::external::s3::services::get_client(&config).await;

match s3_client
.head_bucket()
.bucket(&config.s3_bucket)
.send()
.await
{
Ok(_) => Ok(serde_json::to_value("S3 is up").unwrap()),
Err(err) => Err(anyhow!(serde_json::to_value(err.to_string()).unwrap())),
}
}

pub async fn check_external_services() {
let config = Config::from_env();
let db: DatabaseConnection = Database::connect(&*config.db_url.as_ref().unwrap())
.await
.unwrap();

// Fetch pods and handle the result
let pods_result = crate::external::k8s::services::get_pods().await;

let service: ActiveModel = match pods_result {
Ok(Some(pods)) => {
let pods_json = serde_json::to_value(pods).unwrap();
ServiceCreate {
service_name: ServiceName::RCP,
is_online: true,
details: Some(pods_json),
}
.into()
let k8s: ActiveModel = match check_kubernetes().await {
Ok(pods) => ServiceCreate {
service_name: ServiceName::RCP,
is_online: true,
details: Some(pods),
}
Ok(_) => ServiceCreate {
.into(),
Err(err) => ServiceCreate {
service_name: ServiceName::RCP,
is_online: false,
details: Some(err.to_string().into()),
}
.into(),
};

Entity::insert(k8s).exec(&db).await.unwrap();

let s3: ActiveModel = match check_s3(&config).await {
Ok(s3) => ServiceCreate {
service_name: ServiceName::S3,
is_online: true,
details: None,
details: Some(s3),
}
.into(),
Err(err) => {
let error_json = serde_json::to_value(err.to_string()).unwrap();
ServiceCreate {
service_name: ServiceName::RCP,
is_online: false,
details: Some(error_json),
}
.into()
Err(err) => ServiceCreate {
service_name: ServiceName::S3,
is_online: false,
details: Some(err.to_string().into()),
}
.into(),
};

// Insert the service record into the database
Entity::insert(service).exec(&db).await.unwrap();
Entity::insert(s3).exec(&db).await.unwrap();
}
7 changes: 7 additions & 0 deletions src/external/tus/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ pub(super) async fn handle_post_receive(
_ => return Err(anyhow::anyhow!("Failed to find object")),
};

// Don't update if all parts have been received, it's already 100%
if (obj.clone().unwrap().all_parts_received == true) {
return Ok(PreCreateResponse {
change_file_info: None,
status: "Upload progress updated".to_string(),
});
}
let mut obj: InputObjectDB::ActiveModel = obj.unwrap().into();

obj.processing_message = Set(Some(
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async fn main() {
let app: Router = Router::new()
.route("/healthz", get(common::views::healthz))
.route("/api/config", get(common::views::get_ui_config))
.route("/api/status", get(common::views::get_status))
.with_state(db.clone())
.nest(
"/api/submissions",
Expand Down Expand Up @@ -81,7 +82,7 @@ async fn main() {
_ = tokio::spawn(async move {
loop {
crate::external::services::check_external_services().await;
tokio::time::sleep(Duration::from_secs(300)).await;
tokio::time::sleep(Duration::from_secs(config.interval_external_services)).await;
}
}) => {
println!("Background task finished unexpectedly.");
Expand Down

0 comments on commit 078110c

Please sign in to comment.