Permalink
Browse files

feat(v2): add support for the v2 api

  • Loading branch information...
j-white committed Jun 22, 2017
1 parent 2339c6d commit eb1b1658f35ade8195097c79f097dc1de34caa1c
View
@@ -14,6 +14,7 @@ import {AlarmDAO} from './dao/AlarmDAO';
import {EventDAO} from './dao/EventDAO';
import {NodeDAO} from './dao/NodeDAO';
import {V1FilterProcessor} from './dao/V1FilterProcessor';
import {V2FilterProcessor} from './dao/V2FilterProcessor';
import {OnmsAlarm} from './model/OnmsAlarm';
import {OnmsAlarmType, AlarmTypes} from './model/OnmsAlarmType';
@@ -72,6 +73,7 @@ const DAO = Object.freeze({
EventDAO,
NodeDAO,
V1FilterProcessor,
V2FilterProcessor,
});
/** @hidden */
View
@@ -170,55 +170,54 @@ function CLI() {
.description('List current alarms')
.action((filters) => {
const config = readConfig();
const auth = new API.OnmsAuthConfig(config.username, config.password);
const server = new API.OnmsServer('OpenNMS', config.url, auth);
const http = new Rest.AxiosHTTP(server);
const dao = new DAO.AlarmDAO(http);
const namePattern = /^(.*?)\s+(eq|ne|ilike|like|gt|lt|ge|le|null|notnull)\s+(.*?)$/i;
const symbolPattern = /^(.*?)\s*(=|==|!=|>|<|>=|<=)\s*(.*?)$/i;
const filter = new API.Filter();
for (const f of filters) {
let match = f.match(namePattern);
let attribute;
let comparator;
let value;
if (match) {
attribute = match[1];
comparator = match[2];
value = match[3];
} else {
match = f.match(symbolPattern);
new Client().connect('OpenNMS', config.url, config.username, config.password).then((client) => {
const dao = new DAO.AlarmDAO(client);
const namePattern = /^(.*?)\s+(eq|ne|ilike|like|gt|lt|ge|le|null|notnull)\s+(.*?)$/i;
const symbolPattern = /^(.*?)\s*(=|==|!=|>|<|>=|<=)\s*(.*?)$/i;
const filter = new API.Filter();
for (const f of filters) {
let match = f.match(namePattern);
let attribute;
let comparator;
let value;
if (match) {
attribute = match[1];
comparator = match[2];
value = match[3];
} else {
log.warn('Unable to parse filter "' + f + '"', catCLI);
match = f.match(symbolPattern);
if (match) {
attribute = match[1];
comparator = match[2];
value = match[3];
} else {
log.warn('Unable to parse filter "' + f + '"', catCLI);
}
}
}
if (attribute && comparator) {
for (const type in API.Comparators) {
if (API.Comparators.hasOwnProperty(type)) {
const comp = API.Comparators[type];
if (comp.matches(comparator)) {
filter.restrictions.push(new API.Restriction(attribute, comp, value));
if (attribute && comparator) {
for (const type in API.Comparators) {
if (API.Comparators.hasOwnProperty(type)) {
const comp = API.Comparators[type];
if (comp.matches(comparator)) {
filter.restrictions.push(new API.Restriction(attribute, comp, value));
}
}
}
}
}
}
return dao.find(filter).then((alarms) => {
const headers = ['id', 'severity', 'node', 'count', 'time', 'log'];
console.log(cliff.stringifyObjectRows(formatAlarms(alarms), headers, ['red']));
}).catch((err) => {
if (err.stack) {
log.error(err.stack, err, catCLI);
}
return err;
return dao.find(filter).then((alarms) => {
const headers = ['id', 'severity', 'node', 'count', 'time', 'log'];
console.log(cliff.stringifyObjectRows(formatAlarms(alarms), headers, ['red']));
}).catch((err) => {
if (err.stack) {
log.error(err.stack, err, catCLI);
}
return err;
});
});
});
View
@@ -31,9 +31,6 @@ export interface IOnmsHTTP {
/** the options used when making requests */
options: OnmsHTTPOptions;
/** the filter processor to use when making DAO requests */
filterProcessor: IFilterProcessor;
/**
* Perform an HTTP get to the provided URL.
* @param url the URL to connect to
@@ -53,7 +53,7 @@ export class ServerMetadata {
/** what version of the ReST API does this server support */
public apiVersion() {
return 1;
return this.version.ge('21.0.0') ? 2 : 1;
}
/** a convenient data structure with all capabilities listed */
View
@@ -1,11 +1,14 @@
import {IHasHTTP} from '../api/IHasHTTP';
import {IOnmsHTTP} from '../api/IOnmsHTTP';
import {OnmsError} from '../api/OnmsError';
import {Filter} from '../api/Filter';
import {OnmsHTTPOptions} from '../api/OnmsHTTPOptions';
import {log, catDao} from '../api/Log';
import {Category} from 'typescript-logging';
import {V1FilterProcessor} from './V1FilterProcessor';
import {V2FilterProcessor} from './V2FilterProcessor';
/** @hidden */
// tslint:disable-next-line
@@ -21,6 +24,9 @@ export abstract class AbstractDAO<K, T> {
/** the HTTP implementation to use */
protected http: IOnmsHTTP;
/** the filter processor to use when making DAO requests */
private filterProcessor;
/** construct a DAO instance */
constructor(impl: IHasHTTP | IOnmsHTTP) {
if ((impl as IHasHTTP).http) {
@@ -57,7 +63,7 @@ export abstract class AbstractDAO<K, T> {
// always use application/xml for now in DAO calls
ret.accept = 'application/xml';
if (filter) {
ret.parameters = this.http.filterProcessor.getParameters(filter);
ret.parameters = this.getFilterProcessor().getParameters(filter);
}
return ret;
}
@@ -75,4 +81,26 @@ export abstract class AbstractDAO<K, T> {
const ret = parseInt(from, 10);
return isNaN(ret) ? undefined : ret;
}
/** retrieve the API version from the underlying server */
protected getApiVersion() {
if (this.http.server.metadata === undefined) {
throw new OnmsError('Server meta-data must be populated prior to making DAO calls.');
}
return this.http.server.metadata.apiVersion();
}
/** retrieve filter processor for the current API version */
protected getFilterProcessor() {
if (!this.filterProcessor) {
switch (this.getApiVersion()) {
case 2:
this.filterProcessor = new V2FilterProcessor();
break;
default:
this.filterProcessor = new V1FilterProcessor();
}
}
return this.filterProcessor;
}
}
View
@@ -109,15 +109,15 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
/** get an alarm, given the alarm's ID */
public async get(id: number): Promise<OnmsAlarm> {
const opts = this.getOptions();
return this.http.get('rest/alarms/' + id, opts).then((result) => {
return this.http.get(this.pathToAlarmsEndpoint() + '/' + id, opts).then((result) => {
return this.fromData(result.data);
});
}
/** get an alarm, given a filter */
public async find(filter?: Filter): Promise<OnmsAlarm[]> {
const opts = this.getOptions(filter);
return this.http.get('rest/alarms', opts).then((result) => {
return this.http.get(this.pathToAlarmsEndpoint(), opts).then((result) => {
let data = result.data;
if (this.getCount(data) > 0 && data.alarm) {
@@ -135,4 +135,8 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
});
}
/** get the path to the alarms endpoint for the appropriate API version */
private pathToAlarmsEndpoint() {
return this.getApiVersion() === 2 ? 'api/v2/alarms' : 'rest/alarms';
}
}
@@ -0,0 +1,90 @@
import {IHash} from '../internal/IHash';
import {IFilterProcessor} from '../api/IFilterProcessor';
import {Filter} from '../api/Filter';
import {Comparator, Comparators} from '../api/Comparator';
import {Restriction} from '../api/Restriction';
import {OnmsError} from '../api/OnmsError';
/**
* OpenNMS V2 ReST filter processor
* @module V2FilterProcessor
*/ /** */
export class V2FilterProcessor implements IFilterProcessor {
/** constant used to represent null values in the V2 API */
public static NULL_VALUE = '\u0000';
/** constant used to represent null dates in the V2 API
* this must be explicitly set as the restriction value when using
* either the NULL or NOTNULL comparators on date fields
*/
public static NULL_DATE = '1970-01-01T00:00:00.000+0000';
/**
* given a comparator, convert it to a correspond comparator
* that can be used in the FIQL expression
*/
private static getFIQLComparator(comparator: Comparator) {
switch (comparator) {
case Comparators.EQ:
case Comparators.NULL:
return '==';
case Comparators.NE:
case Comparators.NOTNULL:
return '!=';
case Comparators.GT:
return '=gt=';
case Comparators.LT:
return '=lt=';
case Comparators.GE:
return '=ge=';
case Comparators.LE:
return '=le=';
case Comparators.LIKE:
return '==';
case Comparators.ILIKE:
default:
throw new OnmsError('Unsupported comparator type: ' + comparator);
}
}
/**
* given a restriction, compute the value to use in the FIQL expression
*/
private static getFIQLValue(restriction: Restriction) {
switch (restriction.comparator) {
case Comparators.NULL:
case Comparators.NOTNULL:
return restriction.value === undefined ? V2FilterProcessor.NULL_VALUE : restriction.value;
case Comparators.LIKE:
return '*' + restriction.value + '*';
default:
return restriction.value;
}
}
/** given a filter, return a hash of URL parameters */
public getParameters(filter: Filter) {
const ret = {} as IHash<string>;
if (filter.limit !== undefined) {
ret.limit = '' + filter.limit;
}
const terms = [];
for (const restriction of filter.restrictions) {
const comp = V2FilterProcessor.getFIQLComparator(restriction.comparator);
const value = V2FilterProcessor.getFIQLValue(restriction);
terms.push([restriction.attribute, comp, value].join(''));
}
if (terms.length > 0) {
ret.search = terms.join(';');
}
return ret;
}
}
View
@@ -5,8 +5,6 @@ import {OnmsHTTPOptions} from '../api/OnmsHTTPOptions';
import {OnmsResult} from '../api/OnmsResult';
import {OnmsServer} from '../api/OnmsServer';
import {V1FilterProcessor} from '../dao/V1FilterProcessor';
/** @hidden */
// tslint:disable-next-line
const X2JS = require('x2js');
@@ -29,21 +27,16 @@ export abstract class AbstractHTTP implements IOnmsHTTP {
/** the authorization config associated with this ReST client */
public options: OnmsHTTPOptions;
/** the filter processor to use when making DAO requests */
public filterProcessor = new V1FilterProcessor() as IFilterProcessor;
/** the server metadata we'll use for constructing ReST calls */
private serverObj: OnmsServer;
/** the server associated with this HTTP implementation */
public get server() {
this.assertFilterProcessorExists();
return this.serverObj;
}
public set server(server: OnmsServer) {
this.serverObj = server;
this.assertFilterProcessorExists();
this.onSetServer();
}
@@ -56,7 +49,6 @@ export abstract class AbstractHTTP implements IOnmsHTTP {
constructor(server?: OnmsServer, timeout = 10000) {
this.serverObj = server;
this.timeout = timeout;
this.assertFilterProcessorExists();
}
/** make an HTTP get call -- this should be overridden by the implementation */
@@ -106,15 +98,4 @@ export abstract class AbstractHTTP implements IOnmsHTTP {
// do nothing by default
}
/** make sure the filter processor is initialized */
private assertFilterProcessorExists() {
if (!this.filterProcessor) {
if (this.serverObj && this.serverObj.metadata) {
switch (this.serverObj.metadata.apiVersion()) {
default:
this.filterProcessor = new V1FilterProcessor();
}
}
}
}
}
@@ -26,12 +26,16 @@ const SERVER_PASSWORD='demo';
let opennms : Client, server, auth, mockHTTP, dao : AlarmDAO;
describe('AlarmDAO', () => {
beforeEach(() => {
beforeEach((done) => {
auth = new OnmsAuthConfig(SERVER_USER, SERVER_PASSWORD);
server = new OnmsServer(SERVER_NAME, SERVER_URL, auth);
mockHTTP = new MockHTTP(server);
opennms = new Client(mockHTTP);
dao = new AlarmDAO(mockHTTP);
Client.getMetadata(server, mockHTTP).then((metadata) => {
server.metadata = metadata;
done();
});
});
it('AlarmDAO.get(404725)', () => {
return dao.get(404725).then((alarm) => {
View
@@ -34,12 +34,16 @@ const SERVER_PASSWORD='demo';
let opennms : Client, server, auth, mockHTTP, dao : NodeDAO;
describe('NodeDAO', () => {
beforeEach(() => {
beforeEach((done) => {
auth = new OnmsAuthConfig(SERVER_USER, SERVER_PASSWORD);
server = new OnmsServer(SERVER_NAME, SERVER_URL, auth);
mockHTTP = new MockHTTP(server);
opennms = new Client(mockHTTP);
dao = new NodeDAO(mockHTTP);
Client.getMetadata(server, mockHTTP).then((metadata) => {
server.metadata = metadata;
done();
});
});
it('NodeDAO.get(43, [recurse=false])', () => {
return dao.get(43).then((node) => {
Oops, something went wrong.

0 comments on commit eb1b165

Please sign in to comment.