Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Metrics API and RESTAPI class #46

Merged
merged 12 commits into from
Jun 26, 2020
35 changes: 35 additions & 0 deletions src/apis/metrics/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @packageDocumentation
* @module MetricsAPI
*/
import AvalancheCore from '../../avalanche';
import {RESTAPI, RequestResponseData} from "../../utils/types"

/**
* Class for interacting with a node API that is using the node's MetricsApi.
*
* @category RPCAPIs
*
* @remarks This extends the [[RESTAPI]] class. This class should not be directly called. Instead, use the [[Avalanche.addAPI]] function to register this interface with Avalanche.
*/
class MetricsAPI extends RESTAPI{
/**
*
* @returns Promise for an object containing the metrics response
*/
getMetrics = async ():Promise<string> => {
return this.post("").then((response:RequestResponseData) => {
return response.data as string;
});
}

/**
* This class should not be instantiated directly. Instead use the [[Avalanche.addAPI]] method.
*
* @param core A reference to the Avalanche class
* @param baseurl Defaults to the string "/ext/metrics" as the path to blockchain's baseurl
*/
constructor(core:AvalancheCore, baseurl:string = "/ext/metrics"){ super(core, baseurl); }
}

export default MetricsAPI;
24 changes: 16 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
* @module Avalanche
*/
import AvalancheCore from './avalanche';
import AdminAPI from './apis/admin/api';
import AVMAPI from './apis/avm/api';
import HealthAPI from "./apis/health/api";
import InfoAPI from './apis/info/api';
import KeystoreAPI from './apis/keystore/api';
import MetricsAPI from './apis/metrics/api';
import PlatformAPI from './apis/platform/api';
import AVMAPI from './apis/avm/api';
import AdminAPI from './apis/admin/api';
import * as CoreTypes from './utils/types';
import BinTools from './utils/bintools';
import DB from './utils/db';
import { Defaults } from './utils/types';
import HealthAPI from "./apis/health/api";
import InfoAPI from "./apis/info/api"

/**
* Avalanche.js is middleware for interacting with AVA node RPC APIs.
Expand Down Expand Up @@ -53,6 +54,13 @@ export class Avalanche extends AvalancheCore {
return this.apis["info"] as InfoAPI;
}

/**
* Returns a reference to the Metrics RPC.
*/
Metrics = () => {
return this.apis["metrics"] as MetricsAPI;
}

/**
* Returns a reference to the Keystore RPC for a node. We label it "NodeKeys" to reduce confusion about what it's accessing.
*/
Expand Down Expand Up @@ -96,6 +104,7 @@ export class Avalanche extends AvalancheCore {
this.addAPI('health', HealthAPI);
this.addAPI('info', InfoAPI);
this.addAPI("keystore", KeystoreAPI);
this.addAPI("metrics", MetricsAPI);
this.addAPI("platform", PlatformAPI);
}
}
Expand Down Expand Up @@ -125,10 +134,9 @@ export {BaseTx, CreateAssetTx, OperationTx, UnsignedTx, Tx} from './apis/avm/tx'
export {SigIdx, Signature, Address, UTXOID, InitialStates, AVMConstants, MergeRule, UnixNow} from './apis/avm/types';
export {UTXO, UTXOSet} from './apis/avm/utxos';

export {AdminAPI as Admin};
export {AVMAPI as AVM};
export {HealthAPI as Health};
export {KeystoreAPI as Keystore};
export {MetricsAPI as Metrics};
export {PlatformAPI as Platform};
export {AdminAPI as Admin};
export {HealthAPI as Health};


197 changes: 196 additions & 1 deletion src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,201 @@ export abstract class APIBase {
}
}

export class RESTAPI extends APIBase {
protected contentType:string;
protected acceptType:string;

get = async (baseurl?:string, contentType?:string, acceptType?:string):Promise<RequestResponseData> => {
let ep:string = baseurl ? baseurl : this.baseurl;

let headers:object = {};
if(contentType !== undefined) {
headers["Content-Type"] = contentType;
} else {
headers["Content-Type"] = this.contentType;
}

let acceptTypeStr:string = this.acceptType;
if(acceptType !== undefined) {
headers["Accept"] = acceptType;
} else if(acceptTypeStr !== undefined) {
headers["Accept"] = acceptTypeStr;
}

let axConf:AxiosRequestConfig = {
baseURL:this.core.getProtocol()+"://"+this.core.getIP()+":"+this.core.getPort(),
responseType: 'json'
};

return this.core.get(ep, {}, headers, axConf).then((resp:RequestResponseData) => {
return resp;
});
}

post = async (method:string, params?:Array<object> | object, baseurl?:string, contentType?:string, acceptType?:string):Promise<RequestResponseData> => {
let ep:string = baseurl ? baseurl : this.baseurl;
let rpc:object = {};
rpc["method"] = method;

// Set parameters if exists
if(params) {
rpc['params'] = params;
}

let headers:object = {};
if(contentType !== undefined) {
headers["Content-Type"] = contentType;
} else {
headers["Content-Type"] = this.contentType;
}

let acceptTypeStr:string = this.acceptType;
if(acceptType !== undefined) {
headers["Accept"] = acceptType;
} else if(acceptTypeStr !== undefined) {
headers["Accept"] = acceptTypeStr;
}

let axConf:AxiosRequestConfig = {
baseURL:this.core.getProtocol()+"://"+this.core.getIP()+":"+this.core.getPort(),
responseType: 'json'
};

return this.core.post(ep, {}, JSON.stringify(rpc), headers, axConf).then((resp:RequestResponseData) => {
return resp;
});
}

put = async (method:string, params?:Array<object> | object, baseurl?:string, contentType?:string, acceptType?:string):Promise<RequestResponseData> => {
let ep:string = baseurl ? baseurl : this.baseurl;
let rpc:object = {};
rpc["method"] = method;

// Set parameters if exists
if(params) {
rpc['params'] = params;
}

let headers:object = {};
if(contentType !== undefined) {
headers["Content-Type"] = contentType;
} else {
headers["Content-Type"] = this.contentType;
}

let acceptTypeStr:string = this.acceptType;
if(acceptType !== undefined) {
headers["Accept"] = acceptType;
} else if(acceptTypeStr !== undefined) {
headers["Accept"] = acceptTypeStr;
}

let axConf:AxiosRequestConfig = {
baseURL:this.core.getProtocol()+"://"+this.core.getIP()+":"+this.core.getPort(),
responseType: 'json'
};

return this.core.put(ep, {}, JSON.stringify(rpc), headers, axConf).then((resp:RequestResponseData) => {
return resp;
});
}

delete = async (method:string, params?:Array<object> | object, baseurl?:string, contentType?:string, acceptType?:string):Promise<RequestResponseData> => {
let ep:string = baseurl ? baseurl : this.baseurl;
let rpc:object = {};
rpc["method"] = method;

// Set parameters if exists
if(params) {
rpc['params'] = params;
}

let headers:object = {};
if(contentType !== undefined) {
headers["Content-Type"] = contentType;
} else {
headers["Content-Type"] = this.contentType;
}

let acceptTypeStr:string = this.acceptType;
if(acceptType !== undefined) {
headers["Accept"] = acceptType;
} else if(acceptTypeStr !== undefined) {
headers["Accept"] = acceptTypeStr;
}

let axConf:AxiosRequestConfig = {
baseURL:this.core.getProtocol()+"://"+this.core.getIP()+":"+this.core.getPort(),
responseType: 'json'
};

return this.core.delete(ep, {}, headers, axConf).then((resp:RequestResponseData) => {
return resp;
});
}

patch = async (method:string, params?:Array<object> | object, baseurl?:string, contentType?:string, acceptType?:string):Promise<RequestResponseData> => {
let ep:string = baseurl ? baseurl : this.baseurl;
let rpc:object = {};
rpc["method"] = method;

// Set parameters if exists
if(params) {
rpc['params'] = params;
}

let headers:object = {};
if(contentType !== undefined) {
headers["Content-Type"] = contentType;
} else {
headers["Content-Type"] = this.contentType;
}

let acceptTypeStr:string = this.acceptType;
if(acceptType !== undefined) {
headers["Accept"] = acceptType;
} else if(acceptTypeStr !== undefined) {
headers["Accept"] = acceptTypeStr;
}

let axConf:AxiosRequestConfig = {
baseURL:this.core.getProtocol()+"://"+this.core.getIP()+":"+this.core.getPort(),
responseType: 'json'
};

return this.core.patch(ep, {}, JSON.stringify(rpc), headers, axConf).then((resp:RequestResponseData) => {
return resp;
});
}

/**
* Returns the type of the entity attached to the incoming request
*/
getContentType = ():string => {
return this.contentType;
}

/**
* Returns what type of representation is desired at the client side
*/
getAcceptType = ():string => {
return this.acceptType;
}

/**
*
* @param core Reference to the Avalanche instance using this endpoint
* @param baseurl Path of the APIs baseurl - ex: "/ext/bc/avm"
* @param contentType Optional Determines the type of the entity attached to the incoming request
* @param acceptType Optional Determines the type of representation which is desired on the client side
*/
constructor(core:AvalancheCore, baseurl:string, contentType:string = "application/json;charset=UTF-8", acceptType:string = undefined) {
super(core, baseurl);
this.contentType = contentType;
this.acceptType = acceptType;
}
}

export class JRPCAPI extends APIBase {
protected jrpcVersion:string = "2.0";
protected rpcid = 1;
Expand Down Expand Up @@ -551,4 +746,4 @@ export class Defaults {
"tZGm6RCkeGpVETUTp11DW3UYFZmm69zfqxchpHrSF7wgy8rmw": n12345_contracts
}
};
}
}
2 changes: 0 additions & 2 deletions tests/apis/avm/utxos.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,12 @@ let setMergeTester = (input:UTXOSet, equal:Array<UTXOSet>, notEqual:Array<UTXOSe
let instr:string = JSON.stringify(input.getUTXOIDs().sort());
for(let i:number = 0; i < equal.length; i++){
if(JSON.stringify(equal[i].getUTXOIDs().sort()) != instr){
console.log("equal failed: ", input.getUTXOIDs().sort(), i, equal[i].getUTXOIDs().sort());
cgcardona marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
}

for(let i:number = 0; i < notEqual.length; i++){
if(JSON.stringify(notEqual[i].getUTXOIDs().sort()) == instr){
console.log("notEqual failed: ", input.getUTXOIDs().sort(), i, notEqual[i].getUTXOIDs().sort());
return false;
}
}
Expand Down
44 changes: 44 additions & 0 deletions tests/apis/metrics/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import mockAxios from 'jest-mock-axios';
import { Avalanche } from "src";
import MetricsAPI from "../../../src/apis/metrics/api";

describe("Metrics", () => {
const ip = '127.0.0.1';
const port = 9650;
const protocol = "https";

let avalanche = new Avalanche(ip,port,protocol, 12345, undefined, true);
let metrics:MetricsAPI;

beforeAll(() => {
metrics = new MetricsAPI(avalanche);
});

afterEach(() => {
mockAxios.reset();
});

test("getMetrics", async ()=>{
let result:Promise<string> = metrics.getMetrics();
let payload:string = `
gecko_timestamp_handler_get_failed_bucket{le="100"} 0
gecko_timestamp_handler_get_failed_bucket{le="1000"} 0
gecko_timestamp_handler_get_failed_bucket{le="10000"} 0
gecko_timestamp_handler_get_failed_bucket{le="100000"} 0
gecko_timestamp_handler_get_failed_bucket{le="1e+06"} 0
gecko_timestamp_handler_get_failed_bucket{le="1e+07"} 0
gecko_timestamp_handler_get_failed_bucket{le="1e+08"} 0
gecko_timestamp_handler_get_failed_bucket{le="1e+09"} 0
gecko_timestamp_handler_get_failed_bucket{le="+Inf"} 0
`;
let responseObj = {
data: payload
};

mockAxios.mockResponse(responseObj);
let response:string = await result;

expect(mockAxios.request).toHaveBeenCalledTimes(1);
expect(response).toBe(payload);
});
});