mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
vjudge: add CheckService (#599)
This commit is contained in:
1 parent
807bfb53e3
commit
9da4340a97
8 files changed
+129
-62
No files matched your search
@@ -1,59 +0,0 @@
|
||||
// TODO check more
|
||||
|
||||
import os from 'os';
|
||||
import { Dictionary } from 'lodash';
|
||||
import * as system from './model/system';
|
||||
import db from './service/db';
|
||||
|
||||
const c = {};
|
||||
|
||||
type CheckItem = (context: any, log: Function, warn: Function, error: Function) => Promise<void>;
|
||||
|
||||
const checks: Dictionary<CheckItem> = {
|
||||
async checkDb(ctx, log, warn, error) {
|
||||
try {
|
||||
const coll = db.collection('check');
|
||||
const d = await coll.findOne({ _id: 'check' });
|
||||
if (!d) await coll.insertOne({ _id: 'check', value: 'check' });
|
||||
await coll.createIndex('check');
|
||||
await coll.deleteOne({ _id: 'check' });
|
||||
await coll.drop();
|
||||
} catch (e) {
|
||||
error(`Mongo Error: Database read-write failed.\n${e.message}`);
|
||||
}
|
||||
},
|
||||
// TODO check storage
|
||||
async checkSystem(ctx, log, warn) {
|
||||
const platform = os.platform();
|
||||
if (platform !== 'linux') {
|
||||
warn(`Hydro is running on ${platform}, suggest to use linux4.4+.`);
|
||||
}
|
||||
},
|
||||
async checkMail(ctx, log, warn) {
|
||||
const from = system.get('smtp.from');
|
||||
if (!from) warn('SMTP account is not provided, email verification disabled.');
|
||||
},
|
||||
async checkSetting(ctx, log, warn) {
|
||||
const url = system.get('server.url');
|
||||
if (url === '/') warn("server.url isn't set.");
|
||||
const header = system.get('server.xff');
|
||||
if (header && !ctx.request.ip) warn('IP header seems incorrect.\nCheck dashboard>settings>server.');
|
||||
},
|
||||
};
|
||||
|
||||
export async function start(ctx, log, warn, error, cb: (id: string) => void) {
|
||||
const id = String.random(6);
|
||||
cb(id);
|
||||
for (const name in checks) {
|
||||
if (c[id]) {
|
||||
delete c[id];
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await checks[name](ctx, log, warn, error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancel(id: string) {
|
||||
c[id] = true;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export function register(cli: CAC) {
|
||||
const url = new URL(src);
|
||||
const filename = url.pathname.split('/').pop()!;
|
||||
if (['.tar.gz', '.tgz', '.zip'].find((i) => filename.endsWith(i))) {
|
||||
const name = filename.replace(/(-?\d+\.\d+\.\d+)?(\.tar\.gz|\.zip|\.tgz)$/g, '');
|
||||
const name = filename.replace(/(-?(\d+\.\d+\.\d+|latest))?(\.tar\.gz|\.zip|\.tgz)$/g, '');
|
||||
newAddonPath = path.join(addonDir, name);
|
||||
logger.info(`Downloading ${src} to ${newAddonPath}`);
|
||||
fs.ensureDirSync(newAddonPath);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { GeoIP, ModuleInterfaces } from './interface';
|
||||
import { inject } from './lib/ui';
|
||||
import { Loader } from './loader';
|
||||
import { EventMap } from './service/bus';
|
||||
import type { CheckService } from './service/check';
|
||||
|
||||
export interface Events<C extends Context = Context> extends cordis.Events<C>, EventMap {
|
||||
}
|
||||
@@ -26,6 +27,7 @@ export interface Context {
|
||||
Route: typeof import('./service/server').Route;
|
||||
Connection: typeof import('./service/server').Connection;
|
||||
withHandlerClass: import('./service/server').RouteService['withHandlerClass'];
|
||||
check: CheckService;
|
||||
setTimeout: typeof setTimeout;
|
||||
setInterval: typeof setInterval;
|
||||
setImmediate: typeof setImmediate;
|
||||
|
||||
@@ -48,6 +48,7 @@ export async function apply(ctx: Context) {
|
||||
|
||||
await setting(pending, fail, require('../model/setting'));
|
||||
ctx.plugin(require('../service/monitor'));
|
||||
ctx.plugin(require('../service/check'));
|
||||
await service(pending, fail, ctx);
|
||||
await builtinModel(ctx);
|
||||
await model(pending, fail, ctx);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { inspect } from 'util';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { omit } from 'lodash';
|
||||
import Schema from 'schemastery';
|
||||
import * as check from '../check';
|
||||
import {
|
||||
CannotEditSuperAdminError, NotLaunchedByPM2Error, UserNotFoundError, ValidationError,
|
||||
} from '../error';
|
||||
@@ -14,6 +13,7 @@ import record from '../model/record';
|
||||
import * as setting from '../model/setting';
|
||||
import * as system from '../model/system';
|
||||
import user from '../model/user';
|
||||
import { check } from '../service/check';
|
||||
import {
|
||||
ConnectionHandler, Handler, param, requireSudo, Types,
|
||||
} from '../service/server';
|
||||
@@ -71,7 +71,7 @@ class SystemCheckConnHandler extends ConnectionHandler {
|
||||
const log = (payload: any) => this.send({ type: 'log', payload });
|
||||
const warn = (payload: any) => this.send({ type: 'warn', payload });
|
||||
const error = (payload: any) => this.send({ type: 'error', payload });
|
||||
await check.start(this, log, warn, error, (id) => { this.id = id; });
|
||||
await check.run(this, log, warn, error, (id) => { this.id = id; });
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// TODO check more
|
||||
|
||||
import os from 'os';
|
||||
import { Dictionary } from 'lodash';
|
||||
import { Context, Service } from '../context';
|
||||
import * as system from '../model/system';
|
||||
import db from './db';
|
||||
import storage from './storage';
|
||||
|
||||
type CheckItem = (context: any, log: Function, warn: Function, error: Function) => Promise<void>;
|
||||
|
||||
export class CheckService extends Service {
|
||||
checkers: Dictionary<CheckItem> = {};
|
||||
c = {};
|
||||
addChecker(type: string, checkFunc: CheckItem) {
|
||||
this.checkers[`check${type}`] = checkFunc;
|
||||
this.caller?.on('dispose', () => {
|
||||
delete this.checkers[`check${type}`];
|
||||
});
|
||||
}
|
||||
|
||||
async run(ctx, log, warn, error, cb: (id: string) => void) {
|
||||
const id = String.random(6);
|
||||
cb(id);
|
||||
for (const name in this.checkers) {
|
||||
if (this.c[id]) {
|
||||
delete this.c[id];
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this.checkers[name](ctx, log, warn, error);
|
||||
}
|
||||
}
|
||||
|
||||
async cancel(id: string) {
|
||||
this.c[id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
export const check = new CheckService(app, 'check', true);
|
||||
|
||||
check.addChecker('Db', async (ctx, log, warn, error) => {
|
||||
try {
|
||||
const coll = db.collection('check');
|
||||
const d = await coll.findOne({ _id: 'check' });
|
||||
if (!d) await coll.insertOne({ _id: 'check', value: 'check' });
|
||||
await coll.createIndex('check');
|
||||
await coll.deleteOne({ _id: 'check' });
|
||||
await coll.drop();
|
||||
} catch (e) {
|
||||
error(`Mongo Error: Database read-write failed.\n${e.message}`);
|
||||
}
|
||||
});
|
||||
check.addChecker('Storage', async (ctx, log, warn, error) => {
|
||||
const status = await storage.status();
|
||||
if (!status.status) error(`Connect to ${status.type} Storage ${status.bucket} Error: ${status.error}`);
|
||||
});
|
||||
check.addChecker('System', async (ctx, log, warn) => {
|
||||
const platform = os.platform();
|
||||
if (platform !== 'linux') {
|
||||
warn(`Hydro is running on ${platform}, suggest to use linux4.4+.`);
|
||||
}
|
||||
});
|
||||
check.addChecker('Mail', async (ctx, log, warn) => {
|
||||
const from = system.get('smtp.from');
|
||||
if (!from) warn('SMTP account is not provided, email verification disabled.');
|
||||
});
|
||||
check.addChecker('Setting', async (ctx, log, warn) => {
|
||||
const url = system.get('server.url');
|
||||
if (url === '/') warn("server.url isn't set.");
|
||||
const header = system.get('server.xff');
|
||||
if (header && !ctx.request.ip) warn('IP header seems incorrect.\nCheck dashboard>settings>server.');
|
||||
});
|
||||
|
||||
export async function apply(ctx: Context) {
|
||||
Context.service('check', CheckService);
|
||||
ctx.check = check;
|
||||
}
|
||||
@@ -252,6 +252,15 @@ class RemoteStorageService {
|
||||
CopySource: src,
|
||||
}));
|
||||
}
|
||||
|
||||
async status() {
|
||||
return {
|
||||
type: 'S3',
|
||||
status: !!this.error,
|
||||
error: this.error,
|
||||
bucket: this.bucket,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class LocalStorageService {
|
||||
@@ -329,6 +338,15 @@ class LocalStorageService {
|
||||
await copyFile(src, target);
|
||||
return { etag: target, lastModified: new Date() };
|
||||
}
|
||||
|
||||
async status() {
|
||||
return {
|
||||
type: 'Local',
|
||||
status: !!this.error,
|
||||
error: this.error,
|
||||
bucket: 'Hydro',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let service; // eslint-disable-line import/no-mutable-exports
|
||||
|
||||
@@ -15,6 +15,8 @@ class AccountService {
|
||||
api: IBasicProvider;
|
||||
problemLists: Set<string>;
|
||||
listUpdated = false;
|
||||
working = false;
|
||||
error = null;
|
||||
|
||||
constructor(public Provider: BasicProvider, public account: RemoteAccount) {
|
||||
this.api = new Provider(account, async (data) => {
|
||||
@@ -23,6 +25,7 @@ class AccountService {
|
||||
this.problemLists = Set.union(this.api.entryProblemLists || ['main'], this.account.problemLists || []);
|
||||
this.main().catch((e) => {
|
||||
logger.error(`Error occured in ${account.type}/${account.handle}`);
|
||||
this.error = e.message;
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
@@ -121,6 +124,7 @@ class AccountService {
|
||||
setInterval(() => this.login(), Time.hour);
|
||||
TaskModel.consume({ type: 'remotejudge', subType: this.account.type.split('.')[0] }, this.judge.bind(this), false);
|
||||
const ddocs = await DomainModel.getMulti({ mount: this.account.type.split('.')[0] }).toArray();
|
||||
this.working = true;
|
||||
do {
|
||||
this.listUpdated = false;
|
||||
for (const listName of this.problemLists) {
|
||||
@@ -170,6 +174,18 @@ class VJudgeService extends Service {
|
||||
// TODO dispose session
|
||||
});
|
||||
}
|
||||
|
||||
async checkStatus(onCheckFunc = false) {
|
||||
const res: Record<string, { working: boolean, error?: string, status?: any }> = {};
|
||||
for (const [k, v] of Object.entries(this.pool)) {
|
||||
res[k] = {
|
||||
working: v.working,
|
||||
error: v.error,
|
||||
status: v.api.checkStatus ? await v.api.checkStatus(onCheckFunc) : null,
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export { BasicFetcher } from './fetch';
|
||||
@@ -189,4 +205,15 @@ export async function apply(ctx: Context) {
|
||||
}
|
||||
// });
|
||||
ctx.vjudge = vjudge;
|
||||
ctx.check.addChecker('Vjudge', async (_ctx, log, warn, error) => {
|
||||
const working = [];
|
||||
const pool = await vjudge.checkStatus(true);
|
||||
for (const [k, v] of Object.entries(pool)) {
|
||||
if (!v.working) error(`vjudge worker ${k}: ${v.error}`);
|
||||
else working.push(k);
|
||||
if (v.status) log(`vjudge worker ${k}: ${v.status}`);
|
||||
}
|
||||
if (!working.length) warn('no vjudge worker is working');
|
||||
log(`vjudge working workers: ${working.join(', ')}`);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user