From 96e22e25cfac87bd383ac686de569d9441567e4f Mon Sep 17 00:00:00 2001 From: Sebastian Bittrich Date: Fri, 23 Aug 2024 03:49:17 -0700 Subject: [PATCH] ModelServer & VolumeServer: add health-check (#1233) * VolumeServer: add health-check * ModelServer: add health-check --- CHANGELOG.md | 2 ++ src/servers/common/util.ts | 40 +++++++++++++++++++++++++++- src/servers/model/CHANGELOG.md | 6 +++++ src/servers/model/config.ts | 16 +++++++++-- src/servers/model/server/api-web.ts | 6 ++++- src/servers/model/version.ts | 4 +-- src/servers/volume/CHANGELOG.md | 3 +++ src/servers/volume/config.ts | 12 +++++++-- src/servers/volume/server/version.ts | 4 +-- src/servers/volume/server/web-api.ts | 6 ++++- 10 files changed, 88 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d299c4180..1b30fb985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ Note that since we don't clearly distinguish between a public and private interf - Fix cartoon representation not updated when secondary structure changes - Add Zhang-Skolnick secondary-structure assignment method which handles coarse-grained models (#49) - Calculate bonds for coarse-grained models +- VolumeServer: Add `health-check` endpoint + `healthCheckPath` config prop to report service health +- ModelServer: Add `health-check` endpoint + `healthCheckPath` config prop to report service health ## [v4.5.0] - 2024-07-28 diff --git a/src/servers/common/util.ts b/src/servers/common/util.ts index 3086ffb1f..7ba8ed776 100644 --- a/src/servers/common/util.ts +++ b/src/servers/common/util.ts @@ -1,10 +1,14 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal * @author Alexander Rose + * @author Sebastian Bittrich */ +import * as express from 'express'; +import { promises, constants } from 'fs'; + import { ConsoleLogger } from '../../mol-util/console-logger'; export function getParam(params: any, ...path: string[]): T | undefined { @@ -18,4 +22,38 @@ export function getParam(params: any, ...path: string[]): T | undefined { } catch (e) { ConsoleLogger.error('Config', `Unable to retrieve property ${path.join('.')} from ${JSON.stringify(params)}`); } +} + +/** + * Used to define a dedicated endpoint to monitor service health. Optionally checks whether source data from file system is readable. + * @param res used to write response + * @param paths array of file paths to check, may be empty + */ +export async function healthCheck(res: express.Response, paths: string[]) { + if (paths.length === 0) { + healthCheckResponse(res, true); + return; + } + + for (const path of paths) { + try { + // assert readable file + await promises.access(path, constants.R_OK); + } catch (e) { + ConsoleLogger.error(`Error accessing path ${path}:`, e); + healthCheckResponse(res, false, 'Failed to access data from file system.'); + return; + } + } + healthCheckResponse(res, true); +} + +function healthCheckResponse(res: express.Response, success: boolean, msg?: string) { + res.writeHead(success ? 200 : 500, { + 'Content-Type': 'text/plain', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'X-Requested-With', + }); + res.write(success ? msg || 'true' : msg || 'false'); + res.end(); } \ No newline at end of file diff --git a/src/servers/model/CHANGELOG.md b/src/servers/model/CHANGELOG.md index bb35365f9..b6414cb23 100644 --- a/src/servers/model/CHANGELOG.md +++ b/src/servers/model/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.9.12 +* add `health-check` endpoint + `healthCheckPath` config prop to report service health + +# 0.9.11 +# SDF/MOL2 ligand export: fix atom indices when additional atoms are present + # 0.9.10 * /ligand queries: fix atom count reported by SDF/MOL/MOL2 export diff --git a/src/servers/model/config.ts b/src/servers/model/config.ts index 5fa1823a0..034e80928 100644 --- a/src/servers/model/config.ts +++ b/src/servers/model/config.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal + * @author Sebastian Bittrich */ import * as argparse from 'argparse'; @@ -106,7 +107,12 @@ const DefaultModelServerConfig = { sourceMap: [ ['pdb-cif', 'e:/test/quick/${id}_updated.cif'], // ['pdb-bcif', 'e:/test/quick/${id}.bcif'], - ] as ([string, string] | [string, string, ModelServerFetchFormats])[] + ] as ([string, string] | [string, string, ModelServerFetchFormats])[], + + /** + * Optionally point to files. The service health-check will assert that all are readable and fail otherwise. + */ + healthCheckPath: [] as string[], }; export const ModelServerFetchFormats = ['cif', 'bcif', 'cif.gz', 'bcif.gz'] as const; @@ -198,6 +204,12 @@ function addServerArgs(parser: argparse.ArgumentParser) { `Supported formats: ${ModelServerFetchFormats.join(', ')}` ].join('\n'), }); + parser.add_argument('--healthCheckPath', { + default: DefaultModelServerConfig.healthCheckPath, + action: 'append', + metavar: 'PATH', + help: `File path(s) to use for health-checks. Will test if all files are accessible and report a failed health-check if that's not the case.`, + }); } export type ModelServerConfig = typeof DefaultModelServerConfig diff --git a/src/servers/model/server/api-web.ts b/src/servers/model/server/api-web.ts index d4317347c..ac899e4c7 100644 --- a/src/servers/model/server/api-web.ts +++ b/src/servers/model/server/api-web.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal */ @@ -19,6 +19,7 @@ import { swaggerUiAssetsHandler, swaggerUiIndexHandler } from '../../common/swag import { MultipleQuerySpec, getMultiQuerySpecFilename } from './api-web-multiple'; import { SimpleResponseResultWriter, WebResutlWriter, TarballResponseResultWriter } from '../utils/writer'; import { splitCamelCase } from '../../../mol-util/string'; +import { healthCheck } from '../../common/util'; function makePath(p: string) { return Config.apiPrefix + '/' + p; @@ -169,6 +170,9 @@ export function initWebApi(app: express.Express) { mapQuery(app, q.name, q.definition); } + // Reports server health depending on `healthCheckPath` config prop + app.get(makePath('health-check'), (_, res) => healthCheck(res, ModelServerConfig.healthCheckPath)); + const schema = getApiSchema(); app.get(makePath('openapi.json'), (req, res) => { diff --git a/src/servers/model/version.ts b/src/servers/model/version.ts index a4a16c835..d700f6931 100644 --- a/src/servers/model/version.ts +++ b/src/servers/model/version.ts @@ -1,7 +1,7 @@ /** - * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal */ -export const VERSION = '0.9.11'; \ No newline at end of file +export const VERSION = '0.9.12'; \ No newline at end of file diff --git a/src/servers/volume/CHANGELOG.md b/src/servers/volume/CHANGELOG.md index 82acf6f16..556ee3758 100644 --- a/src/servers/volume/CHANGELOG.md +++ b/src/servers/volume/CHANGELOG.md @@ -1,3 +1,6 @@ +# 0.9.6 +* Add `health-check` endpoint + `healthCheckPath` config prop to report service health. + # 0.9.5 * Better query response box resolution. diff --git a/src/servers/volume/config.ts b/src/servers/volume/config.ts index aac669db9..a46cf4d77 100644 --- a/src/servers/volume/config.ts +++ b/src/servers/volume/config.ts @@ -1,8 +1,9 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal * @author Alexander Rose + * @author Sebastian Bittrich */ import * as argparse from 'argparse'; @@ -15,7 +16,8 @@ const DefaultServerConfig = { defaultPort: 1337, shutdownTimeoutMinutes: 24 * 60, /* a day */ shutdownTimeoutVarianceMinutes: 60, - idMap: [] as [string, string][] + idMap: [] as [string, string][], + healthCheckPath: [] as string[], }; function addLimitsArgs(parser: argparse.ArgumentParser) { @@ -81,6 +83,12 @@ function addServerArgs(parser: argparse.ArgumentParser) { ' By default, Mol* Viewer uses `x-ray` and `em`, but any particular use case may vary. ' ].join('\n'), }); + parser.add_argument('--healthCheckPath', { + default: DefaultServerConfig.healthCheckPath, + action: 'append', + metavar: 'PATH', + help: `File path(s) to use for health-checks. Will test if all files are accessible and report a failed health-check if that's not the case.`, + }); } function addJsonConfigArgs(parser: argparse.ArgumentParser) { diff --git a/src/servers/volume/server/version.ts b/src/servers/volume/server/version.ts index 24ad9feab..aeabd19a6 100644 --- a/src/servers/volume/server/version.ts +++ b/src/servers/volume/server/version.ts @@ -1,2 +1,2 @@ -export const VOLUME_SERVER_VERSION = '0.9.5'; -export const VOLUME_SERVER_HEADER = `VolumeServer ${VOLUME_SERVER_VERSION}, (c) 2018-2020, Mol* contributors`; \ No newline at end of file +export const VOLUME_SERVER_VERSION = '0.9.6'; +export const VOLUME_SERVER_HEADER = `VolumeServer ${VOLUME_SERVER_VERSION}, (c) 2018-2024, Mol* contributors`; \ No newline at end of file diff --git a/src/servers/volume/server/web-api.ts b/src/servers/volume/server/web-api.ts index c6f15a78f..2eaaed01c 100644 --- a/src/servers/volume/server/web-api.ts +++ b/src/servers/volume/server/web-api.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer) * @@ -18,6 +18,7 @@ import { LimitsConfig, ServerConfig } from '../config'; import { interpolate } from '../../../mol-util/string'; import { getSchema, shortcutIconLink } from './web-schema'; import { swaggerUiIndexHandler, swaggerUiAssetsHandler } from '../../common/swagger-ui'; +import { healthCheck } from '../../common/util'; export function init(app: express.Express) { app.locals.mapFile = getMapFileFn(); @@ -32,6 +33,9 @@ export function init(app: express.Express) { // Cell /:src/:id/cell/?text=0|1&space=cartesian|fractional app.get(makePath(':source/:id/cell/?'), (req, res) => queryBox(req, res, getQueryParams(req, true))); + // Reports server health depending on `healthCheckPath` config prop + app.get(makePath('health-check'), (_, res) => healthCheck(res, ServerConfig.healthCheckPath)); + app.get(makePath('openapi.json'), (req, res) => { res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8',