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"]>) {
console.error("BLUJ sethealth", options)
return this.rpcRound("setHealth", options) as ReturnType<
T.Effects["setHealth"]
>

View File

@@ -106,46 +106,106 @@ export class MainLoop {
const { manifest } = this.system
const effects = this.effects
const start = Date.now()
return Object.values(manifest["health-checks"]).map((value) => {
const name = value.name
const interval = setInterval(async () => {
const actionProcedure = value
const timeChanged = Date.now() - start
if (actionProcedure.type === "docker") {
const container = await DockerProcedureContainer.of(
effects,
actionProcedure,
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 Object.entries(manifest["health-checks"]).map(
([healthId, value]) => {
const interval = setInterval(async () => {
const actionProcedure = value
const timeChanged = Date.now() - start
if (actionProcedure.type === "docker") {
const container = await DockerProcedureContainer.of(
effects,
actionProcedure,
manifest.volumes,
)
return (await method(
new PolyfillEffects(effects, this.system.manifest),
timeChanged,
).then((x) => {
if ("result" in x) return x.result
if ("error" in x)
return console.error("Error getting config: " + x.error)
return console.error("Error getting config: " + x["error-code"][1])
})) as any
}
}, EMBASSY_HEALTH_INTERVAL)
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 {
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 tokio::process::Command;
use crate::disk::mount::filesystem::idmapped::IdMapped;
use crate::disk::mount::filesystem::loop_dev::LoopDev;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::prelude::*;
@@ -26,6 +25,7 @@ use crate::status::MainStatus;
use crate::util::clap::FromStrParser;
use crate::util::{new_guid, Invoke};
use crate::{db::model::ExposedUI, service::RunningStatus};
use crate::{disk::mount::filesystem::idmapped::IdMapped, status::health_check::HealthCheckString};
use crate::{echo, ARCH};
#[derive(Clone)]
@@ -605,30 +605,22 @@ async fn set_main_status(context: EffectContext, params: SetMainStatus) -> Resul
#[serde(rename_all = "camelCase")]
struct SetHealth {
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()?;
// 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;
context
@@ -648,14 +640,22 @@ async fn set_health(context: EffectContext, params: SetHealth) -> Result<Value,
match &mut main {
&mut MainStatus::Running { ref mut health, .. }
| &mut MainStatus::BackingUp { ref mut health, .. } => {
health.remove(&params.name);
if let SetHealth {
health.remove(&name);
health.insert(
name,
health_result: Some(health_result),
} = params
{
health.insert(name, health_result);
}
match status {
HealthCheckString::Disabled => HealthCheckResult::Disabled,
HealthCheckString::Passing => HealthCheckResult::Success,
HealthCheckString::Starting => HealthCheckResult::Starting,
HealthCheckString::Warning => HealthCheckResult::Loading {
message: message.unwrap_or_default(),
},
HealthCheckString::Failure => HealthCheckResult::Failure {
error: message.unwrap_or_default(),
},
},
);
}
_ => 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) {
await o.effects.setHealth({
name: o.name,
status: "failing",
status: "failure",
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: "failing",
status: "failure",
message: options.errorMessage,
}
}),
@@ -56,7 +56,7 @@ export async function checkPortListening(
setTimeout(
() =>
resolve({
status: "failing",
status: "failure",
message:
options.timeoutMessage || `Timeout trying to check port ${port}`,
}),

View File

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

View File

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