Skip to content

Commit

Permalink
feat(dao): add alarm ack/unack/escalate/clear support
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Reed committed Jul 13, 2017
1 parent b50d99e commit f44f8d7
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 29 deletions.
49 changes: 49 additions & 0 deletions src/CLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,55 @@ function CLI() {
});
});

const createAlarmAction = (name: string, description: string, ...aliases: string[]) => {
const p = program.command(name + ' <id>');
for (const alias of aliases) {
p.alias(alias);
}
p.description(description);
p.action((id) => {
const config = readConfig();
return new Client().connect('OpenNMS', config.url, config.username, config.password).then((client) => {
return client.alarms()[name](id).then(() => {
console.log('Success!');
return true;
});
}).catch((err) => {
if (program.debug) {
log.error(name + ' failed: ' + err.message, err, catCLI);
} else {
log.error(name + ' failed: ' + err.message, undefined, catCLI);
}
});
});
};

// ack an alarm
program
.command('acknowledge <id>')
.alias('ack')
.description('Acknowledge an alarm')
.option('-u, --user <user>', 'Which user to acknowledge as (only administrators can do this)')
.action((id, options) => {
const config = readConfig();
return new Client().connect('OpenNMS', config.url, config.username, config.password).then((client) => {
return client.alarms().acknowledge(id, options.user).then(() => {
console.log('Success!');
return true;
});
}).catch((err) => {
if (program.debug) {
log.error('Acknowledge failed: ' + err.message, err, catCLI);
} else {
log.error('Acknowledge failed: ' + err.message, undefined, catCLI);
}
});
});

createAlarmAction('unacknowledge', 'Unacknowledge an alarm', 'unack');
createAlarmAction('escalate', 'Escalate an alarm');
createAlarmAction('clear', 'Clear an alarm');

program.parse(process.argv);

if (!process.argv.slice(2).length) {
Expand Down
78 changes: 76 additions & 2 deletions src/dao/AlarmDAO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {EventDAO} from './EventDAO';

import {Filter} from '../api/Filter';
import {IHasHTTP} from '../api/IHasHTTP';
import {IHash} from '../internal/IHash';
import {IOnmsHTTP} from '../api/IOnmsHTTP';
import {OnmsError} from '../api/OnmsError';
import {OnmsHTTPOptions} from '../api/OnmsHTTPOptions';
import {OnmsResult} from '../api/OnmsResult';

import {OnmsAlarm} from '../model/OnmsAlarm';
import {AlarmTypes} from '../model/OnmsAlarmType';
Expand Down Expand Up @@ -111,15 +113,25 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
return alarm;
}

/** get an alarm, given the alarm's ID */
/**
* Fetch an alarm.
*
* @param {number} id - the alarm's ID
* @return an {@link OnmsAlarm}
*/
public async get(id: number): Promise<OnmsAlarm> {
const opts = this.getOptions();
return this.http.get(this.pathToAlarmsEndpoint() + '/' + id, opts).then((result) => {
return this.fromData(result.data);
});
}

/** get an alarm, given a filter */
/**
* Find matching alarms.
*
* @param {Filter} filter - the filter to use when querying
* @return an array of {@link OnmsAlarm}s
*/
public async find(filter?: Filter): Promise<OnmsAlarm[]> {
const opts = this.getOptions(filter);
return this.http.get(this.pathToAlarmsEndpoint(), opts).then((result) => {
Expand All @@ -140,6 +152,54 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
});
}

/**
* Acknowledge an alarm.
*
* @param {number} id - the alarm ID
* @param {string=} user - the user to ack the alarm as (only administrators have the right to do this)
*/
public async acknowledge(id: number, user?: string): Promise<void> {
const parameters = {} as IHash<string>;
parameters.ack = 'true';
if (user !== undefined) {
parameters.ackUser = user;
}
return this.put(this.pathToAlarmsEndpoint() + '/' + id, parameters);
}

/**
* Un-acknowledge an alarm.
*
* @param {number} id - the alarm ID
*/
public async unacknowledge(id: number): Promise<void> {
const parameters = {} as IHash<string>;
parameters.ack = 'false';
return this.put(this.pathToAlarmsEndpoint() + '/' + id, parameters);
}

/**
* Escalate an alarm.
*
* @param {number} id - the alarm ID
*/
public async escalate(id: number): Promise<void> {
const parameters = {} as IHash<string>;
parameters.escalate = 'true';
return this.put(this.pathToAlarmsEndpoint() + '/' + id, parameters);
}

/**
* Clear an alarm.
*
* @param {number} id - the alarm ID
*/
public async clear(id: number): Promise<void> {
const parameters = {} as IHash<string>;
parameters.clear = 'true';
return this.put(this.pathToAlarmsEndpoint() + '/' + id, parameters);
}

/** given an optional filter, generate an {@link OnmsHTTPOptions} object for DAO calls */
protected getOptions(filter?: Filter): OnmsHTTPOptions {
const options = super.getOptions(filter);
Expand All @@ -150,6 +210,20 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
return options;
}

/** call a PUT request in the format the alarm ack API expects */
private async put(url: string, parameters = {} as IHash<string>): Promise<void> {
const opts = this.getOptions();
opts.headers['content-type'] = 'application/x-www-form-urlencoded';
opts.headers.accept = null;
opts.parameters = parameters;
return this.http.put(url, opts).then((result) => {
if (!result.isSuccess) {
throw result;
}
return;
});
}

/** get the path to the alarms endpoint for the appropriate API version */
private pathToAlarmsEndpoint() {
return this.getApiVersion() === 2 ? 'api/v2/alarms' : 'rest/alarms';
Expand Down
34 changes: 19 additions & 15 deletions src/rest/AxiosHTTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,26 @@ export class AxiosHTTP extends AbstractHTTP {
ret.headers = allOptions.headers;
}

if (ret.headers && ret.headers.accept) {
const type = ret.headers.accept;
if (type === 'application/json') {
ret.responseType = 'json';
ret.transformResponse = this.transformJSON;
} else if (type === 'text/plain') {
ret.responseType = 'text';
} else if (type === 'application/xml') {
ret.responseType = 'text';
ret.transformResponse = this.transformXML;
} else {
throw new OnmsError('Unhandled "Accept" header: ' + type);
}
} else {
delete ret.responseType;
ret.headers = ret.headers || {};
if (!ret.headers.accept) {
ret.headers.accept = 'application/json';
}
if (!ret.headers['content-type']) {
ret.headers['content-type'] = 'application/json;charset=utf-8';
}

const type = ret.headers.accept;
if (type === 'application/json') {
ret.responseType = 'json';
ret.transformResponse = this.transformJSON;
} else if (type === 'text/plain') {
ret.responseType = 'text';
delete ret.transformResponse;
} else if (type === 'application/xml') {
ret.responseType = 'text';
ret.transformResponse = this.transformXML;
} else {
throw new OnmsError('Unhandled "Accept" header: ' + type);
}

if (allOptions.parameters) {
Expand Down
32 changes: 21 additions & 11 deletions src/rest/GrafanaHTTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class GrafanaHTTP extends AbstractHTTP {
const query = this.getConfig(options);
query.method = 'PUT';
query.url = realUrl;
query.data = Object.apply({}, query.parameters);
return this.backendSrv.datasourceRequest(query).then((response) => {
let type = 'application/xml';
if (query && query.headers && query.headers.accept) {
Expand All @@ -78,17 +79,26 @@ export class GrafanaHTTP extends AbstractHTTP {
ret.headers = allOptions.headers;
}

if (ret.headers && ret.headers.accept) {
const type = ret.headers.accept;
if (type === 'application/json') {
ret.transformResponse = this.transformJSON;
} else if (type === 'text/plain') {
// allow, but don't do anything special to it
} else if (type === 'application/xml') {
ret.transformResponse = this.transformXML;
} else {
throw new OnmsError('Unhandled "Accept" header: ' + type);
}
ret.headers = ret.headers || {};
if (!ret.headers.accept) {
ret.headers.accept = 'application/json';
}
if (!ret.headers['content-type']) {
ret.headers['content-type'] = 'application/json;charset=utf-8';
}

const type = ret.headers.accept;
if (type === 'application/json') {
ret.responseType = 'json';
ret.transformResponse = this.transformJSON;
} else if (type === 'text/plain') {
ret.responseType = 'text';
delete ret.transformResponse;
} else if (type === 'application/xml') {
ret.responseType = 'text';
ret.transformResponse = this.transformXML;
} else {
throw new OnmsError('Unhandled "Accept" header: ' + type);
}

if (allOptions.parameters && Object.keys(allOptions.parameters).length > 0) {
Expand Down
30 changes: 30 additions & 0 deletions test/dao/AlarmDAO.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ describe('AlarmDAO with v1 API', () => {
expect(alarms.length).toEqual(1);
});
});
it('AlarmDAO.acknowledge(id=404725)', () => {
return dao.acknowledge(404725);
});
it('AlarmDAO.acknowledge(id=404725, user=ranger)', () => {
return dao.acknowledge(404725, 'ranger');
});
it('AlarmDAO.unacknowledge(id=404725)', () => {
return dao.unacknowledge(404725);
});
it('AlarmDAO.escalate(id=404725)', () => {
return dao.escalate(404725);
});
it('AlarmDAO.clear(id=404725)', () => {
return dao.clear(404725);
});
});

describe('AlarmDAO with v2 API', () => {
Expand Down Expand Up @@ -91,4 +106,19 @@ describe('AlarmDAO with v2 API', () => {
expect(alarm.journal.body).toEqual('journal');
});
});
it('AlarmDAO.acknowledge(id=404725)', () => {
return dao.acknowledge(404725);
});
it('AlarmDAO.acknowledge(id=404725, user=ranger)', () => {
return dao.acknowledge(404725, 'ranger');
});
it('AlarmDAO.unacknowledge(id=404725)', () => {
return dao.unacknowledge(404725);
});
it('AlarmDAO.escalate(id=404725)', () => {
return dao.escalate(404725);
});
it('AlarmDAO.clear(id=404725)', () => {
return dao.clear(404725);
});
});
40 changes: 39 additions & 1 deletion test/rest/MockHTTP19.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,44 @@ export class MockHTTP19 extends AbstractHTTP {
return Promise.resolve(result);
}
}
return Promise.reject(OnmsResult.error('Not yet implemented: ' + urlObj.toString()));

return Promise.reject(OnmsResult.error('Not yet implemented: GET ' + urlObj.toString()));
}

public put(url: string, options?: OnmsHTTPOptions) {
const urlObj = new URI(url);
if (options && options.parameters) {
urlObj.search(options.parameters);
}

switch(urlObj.toString()) {
case 'rest/alarms/404725?ack=true': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'rest/alarms/404725?ack=true&ackUser=ranger': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'rest/alarms/404725?ack=false': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'rest/alarms/404725?escalate=true': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'rest/alarms/404725?clear=true': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
}

return Promise.reject(OnmsResult.error('Not yet implemented: PUT ' + urlObj.toString()));
}
}
37 changes: 37 additions & 0 deletions test/rest/MockHTTP21.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,41 @@ export class MockHTTP21 extends AbstractHTTP {
}
return Promise.reject(OnmsResult.error('Not yet implemented: ' + urlObj.toString()));
}

public put(url: string, options?: OnmsHTTPOptions) {
const urlObj = new URI(url);
if (options && options.parameters) {
urlObj.search(options.parameters);
}

switch(urlObj.toString()) {
case 'api/v2/alarms/404725?ack=true': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'api/v2/alarms/404725?ack=true&ackUser=ranger': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'api/v2/alarms/404725?ack=false': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'api/v2/alarms/404725?escalate=true': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
case 'api/v2/alarms/404725?clear=true': {
const result = OnmsResult.ok('');
result.type = 'text/plain';
return Promise.resolve(result);
}
}

return Promise.reject(OnmsResult.error('Not yet implemented: PUT ' + urlObj.toString()));
}
}

0 comments on commit f44f8d7

Please sign in to comment.