Skip to content

Commit

Permalink
U014CQVU4LW can we add an endpoint that behaves [ch5384]
Browse files Browse the repository at this point in the history
add - api endpoint for importer healthcheck
  • Loading branch information
Matthew Eric Bassett committed Jul 10, 2020
1 parent 4502580 commit 4ac1798
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 3 deletions.
75 changes: 75 additions & 0 deletions src/HealthChecker.ts
@@ -0,0 +1,75 @@
import { UtilEither } from "./utils";
import * as BestBlock from "./services/bestblock";

const REQUEST_RATE_MS = 2000;
const REQUEST_TIMEOUT_MS = 5000;
const REQUEST_STALE_BLOCK = 50000;

export type HealthCheckerStatus = "OK" | "DOWN" | "SLOW" | "BLOCK_IS_STALE";

export class HealthChecker {
// given a function that returns a /bestBlock, this class
// will spawn a thread that checks to see that /bestBlock
// is changing over time.
// If it is not, then we have an error!

healthCheckerFunc : () => Promise<UtilEither<BestBlock.CardanoFrag>>;

lastBlock : UtilEither<BestBlock.CardanoFrag>;
lastTime : number;
lastGoodBlockChange : number;
currentBlock : [number, UtilEither<BestBlock.CardanoFrag>];

constructor( bestBlockFunc : () => Promise<UtilEither<BestBlock.CardanoFrag>> ) {
this.healthCheckerFunc = bestBlockFunc;
const currentTime = Date.now();

this.lastBlock = { kind: 'error', errMsg: 'init' };
this.lastTime = currentTime;
this.lastGoodBlockChange = currentTime;
this.currentBlock = [currentTime, { kind: 'error', errMsg: 'init' }];

setInterval( this.checkHealth, REQUEST_RATE_MS);

}

checkHealth = async () => {
let mBlock : UtilEither<BestBlock.CardanoFrag> = { kind: 'error', errMsg: 'function failed' };
try {
mBlock = await this.healthCheckerFunc();
} catch {
}
const currentTime = Date.now();

const [currentSavedTime, currentMBlock] = this.currentBlock;

if(currentMBlock.kind !== this.lastBlock.kind)
this.lastBlock = currentMBlock;
if(currentMBlock.kind === 'ok' && this.lastBlock.kind === 'ok')
if(currentMBlock.value.blockHeight !== this.lastBlock.value.blockHeight){
this.lastBlock = currentMBlock;
this.lastGoodBlockChange = currentTime;
}


this.lastTime = currentSavedTime;
this.currentBlock = [currentTime, mBlock];
}

getStatus = ():HealthCheckerStatus => {
const [currentSavedTime, currentMBlock] = this.currentBlock;
if (currentMBlock.kind !== 'ok')
return "DOWN";
if (currentSavedTime - this.lastTime > REQUEST_TIMEOUT_MS)
return "SLOW";
if (currentSavedTime - this.lastGoodBlockChange > REQUEST_STALE_BLOCK)
if(this.lastBlock.kind === 'ok')
if(this.lastBlock.value.blockHeight === currentMBlock.value.blockHeight)
return "BLOCK_IS_STALE";
return "OK";

}

}


16 changes: 16 additions & 0 deletions src/index.ts
Expand Up @@ -15,9 +15,13 @@ import { askBlockNumByHash, askBlockNumByTxHash, askTransactionHistory } from ".
import { askFilterUsedAddresses } from "./services/filterUsedAddress";
import { askUtxoSumForAddresses } from "./services/utxoSumForAddress";

import { HealthChecker } from "./HealthChecker";


const pool = new Pool({user: 'hasura', host:'/tmp/', database: 'cexplorer'});

const healthChecker = new HealthChecker(askBestBlock);

const router = express();

const middlewares = [ middleware.handleCors
Expand Down Expand Up @@ -231,6 +235,18 @@ const routes : Route[] = [ { path: '/v2/bestblock'
, method: "post"
, handler: txHistory
}
, { path: '/v2/importerhealthcheck'
, method: "get"
, handler: async (req: Request, res: Response) => {
const status = healthChecker.getStatus()
if (status === 'OK')
res.send({ code: 200, message: "Importer is OK" });
else if (status === 'SLOW')
res.send({ code: 200, message: "Importer seems OK. Not enough time has passed since last valid request." });
else
throw new Error(status);
}
}
, { path: '/status'
, method: "get"
, handler: async (req: Request, res: Response) => {
Expand Down
6 changes: 3 additions & 3 deletions src/services/bestblock.ts
Expand Up @@ -3,18 +3,18 @@ import { Request, Response } from "express";

import { assertNever, contentTypeHeaders, graphqlEndpoint, UtilEither} from "../utils";

interface CardanoFrag {
export interface CardanoFrag {
blockHeight: number;
currentEpoch: EpochFrag;
slotDuration: number;
}

interface EpochFrag {
export interface EpochFrag {
blocks: BlockFrag[];
number: number;
}

interface BlockFrag {
export interface BlockFrag {
hash: string;
number: number;
slotWithinEpoch: number;
Expand Down
29 changes: 29 additions & 0 deletions tests/healthCheck.test.ts
@@ -0,0 +1,29 @@
import axios from 'axios';
import { expect } from 'chai';
import { config, Config } from './config'

import { UtilEither } from "../src/utils";
import * as BestBlock from "../src/services/bestblock";
import { HealthChecker } from "../src/HealthChecker";

const endpoint = config.apiUrl;

describe('/importerhealthcheck', function() {
this.timeout(10000);
it('returns', async function() {
let result = await axios.get(endpoint+"v2/importerhealthcheck");
expect(result.data).to.have.property('message');
expect(result.data.message).to.be.eql("Importer is OK");
});
it('fails for broken graphql api', async function () {
const badFunc = async () : Promise<UtilEither<BestBlock.CardanoFrag>> => {
return { kind: 'error', errMsg: 'haha I don\'t work' };

};
const healthChecker = new HealthChecker(badFunc);
const status = healthChecker.getStatus();
expect(status).to.not.be.eql('OK');


});
});

0 comments on commit 4ac1798

Please sign in to comment.