Skip to content

Commit

Permalink
feat(watcher): create a route to healthcheck /version
Browse files Browse the repository at this point in the history
  • Loading branch information
Yagora committed Aug 13, 2024
1 parent cd81a26 commit d5ce1c7
Show file tree
Hide file tree
Showing 12 changed files with 1,440 additions and 539 deletions.
1,807 changes: 1,277 additions & 530 deletions watcher/package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion watcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@
"debug": "^4.3.4",
"dotenv": "^16.0.3",
"ethers": "^5.7.2",
"kcors": "^2.2.2",
"koa": "^2.15.3",
"koa-router": "^12.0.1",
"mongoose": "^6.8.3",
"redis": "^4.5.1",
"socket.io": "^4.5.4"
"socket.io": "^4.5.4",
"supertest": "^7.0.0",
"yup": "^1.4.0"
},
"devDependencies": {
"eslint": "^8.31.0",
Expand Down
18 changes: 17 additions & 1 deletion watcher/src/app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
const http = require('http');
const Koa = require('koa');
const corsMiddleware = require('kcors')();

const { errorMiddleware } = require('./controllers/error');
const { router } = require('./controllers/router');
const config = require('./config');
const ethereum = require('./loaders/ethereum');
const socket = require('./loaders/socket');
Expand Down Expand Up @@ -29,7 +35,16 @@ if (!wsHost) throw Error('missing wsHost');
if (!httpHost) throw Error('missing httpHost');
if (!hubAddress) throw Error('missing hubAddress');

socket.init();
const koa = new Koa();
const app = http.createServer(koa.callback());

socket.init(app);

koa
.use(errorMiddleware)
.use(corsMiddleware)
.use(router.routes())
.use(router.allowedMethods());

const start = async ({ replayer = true, syncWatcher = true } = {}) => {
try {
Expand Down Expand Up @@ -106,4 +121,5 @@ const stop = async () => {
module.exports = {
stop,
start,
app,
};
5 changes: 5 additions & 0 deletions watcher/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { getLogger } = require('./utils/logger');
const logger = getLogger('config');

const {
PORT,
MONGO_HOST,
REDIS_HOST,
FLAVOUR,
Expand Down Expand Up @@ -113,11 +114,14 @@ if (!chain.httpHost) {
throw Error('missing ethereum RPC endpoint ETH_RPC_HOST');
}

const serverPort = parseInt(PORT, 10) || 3000;

logger.log('chain', chain);
logger.log('flavour', flavour);
logger.log('mongo', mongo);
logger.log('redis', redis);
logger.log('runtime', runtime);
logger.log('serverPort', serverPort);

module.exports = {
abi,
Expand All @@ -126,4 +130,5 @@ module.exports = {
mongo,
redis,
runtime,
serverPort,
};
47 changes: 47 additions & 0 deletions watcher/src/controllers/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const { getLogger, APP_NAMESPACE } = require('../utils/logger');
const logger = getLogger('controllers:error');
const {
ValidationError,
InternalError,
ObjectNotFoundError,
errorHandler,
} = require('../utils/error');

const errorMiddleware = async (ctx, next) => {
try {
await next();
} catch (error) {
errorHandler(error, { type: 'request', request: ctx.request });
if (error instanceof InternalError) {
logger.log('InternalError:', error.message);
ctx.status = 500;
ctx.body = {
ok: false,
error: 'Something went wrong, you should retry later.',
};
return;
}
if (error instanceof ValidationError) {
logger.log('ValidationError:', error.message);
ctx.status = 400;
ctx.body = { ok: false, error: error.message };
return;
}
if (error instanceof ObjectNotFoundError) {
logger.log('ObjectNotFoundError:', error.message);
ctx.status = 404;
ctx.body = { ok: false, error: error.message };
return;
}
logger.log('Error:', error.message);
ctx.status = 500;
ctx.body = {
ok: false,
error: 'Internal error',
};
}
};

module.exports = {
errorMiddleware,
};
19 changes: 19 additions & 0 deletions watcher/src/controllers/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const Router = require('koa-router');
const { getVersion } = require('../services/version');
const { getLogger } = require('../utils/logger');
const logger = getLogger('controllers:router');


const router = new Router();

// version
router.get('/version', async (ctx) => {
logger.log('GET /version');
const version = await getVersion();
ctx.body = { ok: true, version };
});


module.exports = {
router,
};
8 changes: 7 additions & 1 deletion watcher/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
const { start } = require('./app');
const { start, app } = require('./app');
const { serverPort } = require('./config');
const { getLogger, APP_NAMESPACE } = require('./utils/logger');
const logger = getLogger(APP_NAMESPACE);

app.listen(serverPort);
logger.log(`Server listening on port ${serverPort}`);

start();
7 changes: 2 additions & 5 deletions watcher/src/loaders/socket.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const http = require('http');
const socketIo = require('socket.io');
const { createClient } = require('redis');
const { createAdapter } = require('@socket.io/redis-adapter');
Expand All @@ -8,16 +7,14 @@ const { throwIfMissing, errorHandler } = require('../utils/error');

const logger = getLogger('socket');

const server = http.createServer();

let ws = null;

const getWs = () => {
if (ws === null) throw new Error('socket not initialized');
return ws;
};

const init = async () => {
const init = async (server = throwIfMissing()) => {
try {
logger.log('init socket');
const redisConfig = config.redis;
Expand All @@ -31,7 +28,7 @@ const init = async () => {
subClient.on('end', () => logger.log('subClient end'));
await Promise.all[(pubClient.connect(), subClient.connect())];
const redisAdapter = createAdapter(pubClient, subClient);
ws = socketIo(server);
ws = socketIo(server, { path: '/ws', cors: { origin: '*' } });
ws.adapter(redisAdapter);
logger.log('socket initialized');
} catch (error) {
Expand Down
7 changes: 7 additions & 0 deletions watcher/src/services/version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { version } = require('../../package.json');

const getVersion = () => version;

module.exports = {
getVersion,
};
21 changes: 21 additions & 0 deletions watcher/src/utils/error.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { ValidationError } = require('yup');
const { getLogger, APP_NAMESPACE } = require('./logger');
const { sleep } = require('./utils');

Expand Down Expand Up @@ -38,6 +39,24 @@ class Web3ProviderError extends InternalError {
}
}

class OperationalError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}

class ObjectNotFoundError extends OperationalError {
constructor(message, originalError) {
super(message);
this.name = this.constructor.name;
this.originalError = originalError;
if (originalError && typeof originalError === 'object') {
Object.assign(this, getPropsToCopy(originalError));
}
}
}

const wrapEthCall = async (promise) => {
try {
return await promise;
Expand Down Expand Up @@ -75,6 +94,8 @@ const errorHandler = async (error, context) => {
};

module.exports = {
ValidationError,
ObjectNotFoundError,
InternalError,
Web3ProviderError,
wrapEthCall,
Expand Down
3 changes: 3 additions & 0 deletions watcher/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ const setCheckpointToLastBlock = async (dbName) => {
);
};

const parseResult = (res) => ({ ...res, data: JSON.parse(res.text) });

module.exports = {
addApporders,
addDatasetorders,
Expand All @@ -354,6 +356,7 @@ module.exports = {
getMatchableRequestorder,
transferResourceERC721,
timestampRegex,
parseResult,
bytes32Regex,
addressRegex,
APPORDERS_COLLECTION,
Expand Down
30 changes: 29 additions & 1 deletion watcher/test/watcher-common.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const supertest = require('supertest');
const ethers = require('ethers');
const { utils, IExec } = require('iexec');
const socket = require('../src/loaders/socket');
// jest spies
const socketEmitSpy = jest.spyOn(socket, 'emit');
const { start, stop } = require('../src/app');
const { start, stop, app } = require('../src/app');
const { replayPastOnly } = require('../src/controllers/replayer');
const { chain } = require('../src/config');
const { sleep } = require('../src/utils/utils');
Expand All @@ -21,6 +22,7 @@ const {
getMatchableRequestorder,
transferResourceERC721,
timestampRegex,
parseResult,
bytes32Regex,
APPORDERS_COLLECTION,
DATASETORDERS_COLLECTION,
Expand All @@ -36,6 +38,8 @@ const { init: ethereumInit } = require('../src/loaders/ethereum');
jest.setTimeout(120000);

const PROCESS_TRIGGERED_EVENT_TIMEOUT = 1000;
const NOT_FOUND_ERROR_STATUS = 404;
const OK_STATUS = 200;

let chainId;
const chainUrl = chain.httpHost;
Expand All @@ -46,6 +50,8 @@ const rpc = new ethers.providers.JsonRpcProvider(chainUrl);
const wallet = new ethers.Wallet(PRIVATE_KEY, rpc);

let iexec;
let serverHttp;
let request;
const signer = utils.getSignerFromPrivateKey(chainUrl, PRIVATE_KEY);

beforeAll(async () => {
Expand All @@ -67,6 +73,28 @@ beforeAll(async () => {
await iexec.wallet.getAddress(),
);
await iexec.account.withdraw(stake);
serverHttp = app.listen();
request = supertest(serverHttp);
});

afterAll(async () => {
serverHttp.close();
});

describe('API', () => {
describe('Common', () => {
test('GET /version', async () => {
const { data, status } = await request.get('/version').then(parseResult);
expect(status).toBe(OK_STATUS);
expect(data.ok).toBe(true);
expect(data.version).toBeDefined();
});
test('GET /foo (not found)', async () => {
const res = await request.get('/foo');
expect(res.status).toBe(NOT_FOUND_ERROR_STATUS);
expect(res.text).toBe("Not Found");
});
});
});

describe('Watcher', () => {
Expand Down

0 comments on commit d5ce1c7

Please sign in to comment.