feat: Get the health checks for the js

This commit is contained in:
J H
2024-03-07 11:38:59 -07:00
parent 14be2fa344
commit efbbaa5741
10 changed files with 159 additions and 80 deletions

View File

@@ -268,6 +268,7 @@ export class HostSystemStartOs implements Effects {
> >
} }
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) { setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
console.error("BLUJ sethealth", options)
return this.rpcRound("setHealth", options) as ReturnType< return this.rpcRound("setHealth", options) as ReturnType<
T.Effects["setHealth"] T.Effects["setHealth"]
> >

View File

@@ -106,46 +106,106 @@ export class MainLoop {
const { manifest } = this.system const { manifest } = this.system
const effects = this.effects const effects = this.effects
const start = Date.now() const start = Date.now()
return Object.values(manifest["health-checks"]).map((value) => { return Object.entries(manifest["health-checks"]).map(
const name = value.name ([healthId, value]) => {
const interval = setInterval(async () => { const interval = setInterval(async () => {
const actionProcedure = value const actionProcedure = value
const timeChanged = Date.now() - start const timeChanged = Date.now() - start
if (actionProcedure.type === "docker") { if (actionProcedure.type === "docker") {
const container = await DockerProcedureContainer.of( const container = await DockerProcedureContainer.of(
effects, effects,
actionProcedure, actionProcedure,
manifest.volumes, manifest.volumes,
)
const executed = await container.exec([
actionProcedure.entrypoint,
...actionProcedure.args,
JSON.stringify(timeChanged),
])
const stderr = executed.stderr.toString()
if (stderr)
console.error(`Error running health check ${value.name}: ${stderr}`)
return executed.stdout.toString()
} else {
const moduleCode = await this.system.moduleCode
const method = moduleCode.health?.[value.name]
if (!method)
return console.error(
`Expecting that thejs health check ${value.name} exists`,
) )
return (await method( const executed = await container.exec([
new PolyfillEffects(effects, this.system.manifest), actionProcedure.entrypoint,
timeChanged, ...actionProcedure.args,
).then((x) => { JSON.stringify(timeChanged),
if ("result" in x) return x.result ])
if ("error" in x) const stderr = executed.stderr.toString()
return console.error("Error getting config: " + x.error) if (stderr)
return console.error("Error getting config: " + x["error-code"][1]) console.error(
})) as any `Error running health check ${value.name}: ${stderr}`,
} )
}, EMBASSY_HEALTH_INTERVAL) return executed.stdout.toString()
} else {
actionProcedure
const moduleCode = await this.system.moduleCode
const method = moduleCode.health?.[healthId]
if (!method) {
await effects.setHealth({
name: healthId,
status: "failure",
message: `Expecting that thejs health check ${healthId} exists`,
})
return
}
return { name, interval } const result = await method(
}) new PolyfillEffects(effects, this.system.manifest),
timeChanged,
)
if ("result" in result) {
await effects.setHealth({
name: healthId,
status: "passing",
})
return
}
if ("error" in result) {
await effects.setHealth({
name: healthId,
status: "failure",
message: result.error,
})
return
}
if (!("error-code" in result)) {
await effects.setHealth({
name: healthId,
status: "failure",
message: `Unknown error type ${JSON.stringify(result)}`,
})
return
}
const [code, message] = result["error-code"]
if (code === 59) {
await effects.setHealth({
name: healthId,
status: "disabled",
message,
})
return
}
if (code === 60) {
await effects.setHealth({
name: healthId,
status: "starting",
message,
})
return
}
if (code === 61) {
await effects.setHealth({
name: healthId,
status: "warning",
message,
})
return
}
await effects.setHealth({
name: healthId,
status: "failure",
message: `${result["error-code"][0]}: ${result["error-code"][1]}`,
})
return
}
}, EMBASSY_HEALTH_INTERVAL)
return { name: healthId, interval }
},
)
} }
} }

View File

@@ -13,7 +13,6 @@ use patch_db::json_ptr::JsonPointer;
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler}; use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
use tokio::process::Command; use tokio::process::Command;
use crate::disk::mount::filesystem::idmapped::IdMapped;
use crate::disk::mount::filesystem::loop_dev::LoopDev; use crate::disk::mount::filesystem::loop_dev::LoopDev;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard; use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::prelude::*; use crate::prelude::*;
@@ -26,6 +25,7 @@ use crate::status::MainStatus;
use crate::util::clap::FromStrParser; use crate::util::clap::FromStrParser;
use crate::util::{new_guid, Invoke}; use crate::util::{new_guid, Invoke};
use crate::{db::model::ExposedUI, service::RunningStatus}; use crate::{db::model::ExposedUI, service::RunningStatus};
use crate::{disk::mount::filesystem::idmapped::IdMapped, status::health_check::HealthCheckString};
use crate::{echo, ARCH}; use crate::{echo, ARCH};
#[derive(Clone)] #[derive(Clone)]
@@ -605,30 +605,22 @@ async fn set_main_status(context: EffectContext, params: SetMainStatus) -> Resul
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct SetHealth { struct SetHealth {
name: HealthCheckId, name: HealthCheckId,
health_result: Option<HealthCheckResult>, status: HealthCheckString,
message: Option<String>,
} }
async fn set_health(context: EffectContext, params: SetHealth) -> Result<Value, Error> { async fn set_health(
context: EffectContext,
SetHealth {
name,
status,
message,
}: SetHealth,
) -> Result<Value, Error> {
dbg!(&name);
dbg!(&status);
dbg!(&message);
let context = context.deref()?; let context = context.deref()?;
// TODO DrBonez + BLU-J Need to change the type from
// ```rs
// #[serde(tag = "result")]
// pub enum HealthCheckResult {
// Success,
// Disabled,
// Starting,
// Loading { message: String },
// Failure { error: String },
// }
// ```
// to
// ```ts
// setHealth(o: {
// name: string
// status: HealthStatus
// message?: string
// }): Promise<void>
// ```
let package_id = &context.id; let package_id = &context.id;
context context
@@ -648,14 +640,22 @@ async fn set_health(context: EffectContext, params: SetHealth) -> Result<Value,
match &mut main { match &mut main {
&mut MainStatus::Running { ref mut health, .. } &mut MainStatus::Running { ref mut health, .. }
| &mut MainStatus::BackingUp { ref mut health, .. } => { | &mut MainStatus::BackingUp { ref mut health, .. } => {
health.remove(&params.name); health.remove(&name);
if let SetHealth {
health.insert(
name, name,
health_result: Some(health_result), match status {
} = params HealthCheckString::Disabled => HealthCheckResult::Disabled,
{ HealthCheckString::Passing => HealthCheckResult::Success,
health.insert(name, health_result); HealthCheckString::Starting => HealthCheckResult::Starting,
} HealthCheckString::Warning => HealthCheckResult::Loading {
message: message.unwrap_or_default(),
},
HealthCheckString::Failure => HealthCheckResult::Failure {
error: message.unwrap_or_default(),
},
},
);
} }
_ => return Ok(()), _ => return Ok(()),
}; };

View File

@@ -22,3 +22,13 @@ impl std::fmt::Display for HealthCheckResult {
} }
} }
} }
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum HealthCheckString {
Passing,
Disabled,
Starting,
Warning,
Failure,
}

View File

@@ -47,10 +47,10 @@ export function healthCheck(o: {
} catch (e) { } catch (e) {
await o.effects.setHealth({ await o.effects.setHealth({
name: o.name, name: o.name,
status: "failing", status: "failure",
message: asMessage(e), message: asMessage(e),
}) })
currentValue.lastResult = "failing" currentValue.lastResult = "failure"
} }
} }
}) })

View File

@@ -48,7 +48,7 @@ export async function checkPortListening(
return { status: "passing", message: options.successMessage } return { status: "passing", message: options.successMessage }
} }
return { return {
status: "failing", status: "failure",
message: options.errorMessage, message: options.errorMessage,
} }
}), }),
@@ -56,7 +56,7 @@ export async function checkPortListening(
setTimeout( setTimeout(
() => () =>
resolve({ resolve({
status: "failing", status: "failure",
message: message:
options.timeoutMessage || `Timeout trying to check port ${port}`, options.timeoutMessage || `Timeout trying to check port ${port}`,
}), }),

View File

@@ -19,14 +19,17 @@ export const checkWebUrl = async (
} = {}, } = {},
): Promise<CheckResult> => { ): Promise<CheckResult> => {
return Promise.race([fetch(url), timeoutPromise(timeout)]) return Promise.race([fetch(url), timeoutPromise(timeout)])
.then((x) => ({ .then(
status: "passing" as const, (x) =>
message: successMessage, ({
})) status: "passing",
message: successMessage,
}) as const,
)
.catch((e) => { .catch((e) => {
console.warn(`Error while fetching URL: ${url}`) console.warn(`Error while fetching URL: ${url}`)
console.error(JSON.stringify(e)) console.error(JSON.stringify(e))
console.error(e.toString()) console.error(e.toString())
return { status: "failing" as const, message: errorMessage } return { status: "failure" as const, message: errorMessage }
}) })
} }

View File

@@ -29,7 +29,7 @@ export const runHealthScript = async (
console.warn(errorMessage) console.warn(errorMessage)
console.warn(JSON.stringify(e)) console.warn(JSON.stringify(e))
console.warn(e.toString()) console.warn(e.toString())
throw { status: "failing", message: errorMessage } as CheckResult throw { status: "failure", message: errorMessage } as CheckResult
}) })
return { return {
status: "passing", status: "passing",

View File

@@ -121,7 +121,7 @@ export class Daemons<Manifest extends SDKManifest, Ids extends string> {
const response = await Promise.resolve(daemon.ready.fn()).catch( const response = await Promise.resolve(daemon.ready.fn()).catch(
(err) => (err) =>
({ ({
status: "failing", status: "failure",
message: "message" in err ? err.message : String(err), message: "message" in err ? err.message : String(err),
}) as CheckResult, }) as CheckResult,
) )

View File

@@ -133,7 +133,12 @@ export type Daemon = {
[DaemonProof]: never [DaemonProof]: never
} }
export type HealthStatus = "passing" | "warning" | "failing" | "disabled" export type HealthStatus =
| `passing`
| `disabled`
| `starting`
| `warning`
| `failure`
export type SmtpValue = { export type SmtpValue = {
server: string server: string