RIFT-14874: Allow deny of duplicate events in loggin
[osm/UI.git] / skyquake / plugins / logging / api / logging.js
1
2 /*
3 *
4 * Copyright 2016 RIFT.IO Inc
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19 'use strict';
20 var Promise = require('bluebird');
21 var utils = require('../../../framework/core/api_utils/utils.js');
22 var request = utils.request;
23 var constants = require('../../../framework/core/api_utils/constants.js');
24 var _ = require('lodash');
25 var APIVersion = '/v2';
26 var transforms = require('./transforms.js');
27
28 var foreverOn = true;
29
30 var Config = {};
31 var Console = {};
32 var Filter = {};
33 var Operational = {};
34 var Sink = {};
35 var SysLogViewer = {};
36 var Aggregate = {};
37
38 var Test = {};
39
40 // Helper functions
41 // TODO: Consolidate the build functions, provide method type as arg
42
43 function buildGetRequestOptions(req, endpoint) {
44 var headers = _.extend({},
45 constants.HTTP_HEADERS.accept.data, {
46 'Authorization': req.get('Authorization')
47 });
48 var api_server = req.query["api_server"];
49 var requestOptions = {
50 url: utils.confdPort(api_server) + endpoint,
51 method: 'GET',
52 headers: headers,
53 forever: constants.FOREVER_ON,
54 rejectUnauthorized: false
55 };
56 return requestOptions;
57 }
58
59 function buildPutRequestOptions(req, endpoint, jsonData) {
60 var headers = _.extend({},
61 constants.HTTP_HEADERS.accept.data,
62 constants.HTTP_HEADERS.content_type.data, {
63 'Authorization': req.get('Authorization')
64 });
65 var api_server = req.query["api_server"];
66 var requestOptions = {
67 url: utils.confdPort(api_server) + endpoint,
68 method: 'PUT',
69 headers: headers,
70 forever: constants.FOREVER_ON,
71 rejectUnauthorized: false,
72 json: jsonData
73 };
74 return requestOptions;
75 }
76
77
78 function buildDeleteRequestOptions(req, endpoint) {
79 var headers = _.extend({},
80 constants.HTTP_HEADERS.accept.data,
81 constants.HTTP_HEADERS.content_type.data, {
82 'Authorization': req.get('Authorization')
83 });
84 var api_server = req.query["api_server"];
85 var requestOptions = {
86 url: utils.confdPort(api_server) + endpoint,
87 method: 'DELETE',
88 headers: headers,
89 forever: constants.FOREVER_ON,
90 rejectUnauthorized: false
91 };
92 return requestOptions;
93 }
94
95 /**
96 * Used for simulating latency
97 */
98 function resolve_with_delay(resolve, data, delay) {
99 return setTimeout(function() {
100 resolve(data)
101 }, delay);
102 }
103
104 /**
105 * This function provides the default callback for requests
106 */
107 function requestCallback(resolve, reject, transformFunc) {
108 return function(error, response, body) {
109 if (utils.validateResponse('', error, response, body, resolve, reject)) {
110 if (transformFunc) {
111 var data = transformFunc(response.body);
112 } else {
113 var data = JSON.stringify(response.body);
114 }
115 return resolve({
116 statusCode: response.statusCode,
117 data: data
118 });
119 };
120 };
121 }
122
123 function handleGetRequest(req, endpoint, responseTransform) {
124 return new Promise(function(resolve, reject) {
125 request(
126 buildGetRequestOptions(req, endpoint),
127 requestCallback(resolve, reject, responseTransform)
128 );
129 });
130 }
131
132 /**
133 *
134 */
135 // TODO: Add arg for transform function to transfrm req.body to json data
136 // Right now we'll just pass the request through, until we need to implement
137 // a transformer
138 function handlePutRequest(req, endpoint, body) {
139 return new Promise(function(resolve, reject) {
140 request(
141 buildPutRequestOptions(req, endpoint, body||req.body),
142 requestCallback(resolve, reject)
143 );
144 });
145 }
146
147 function handleDeleteRequest(req, endpoint, body) {
148 return new Promise(function(resolve, reject) {
149 request(
150 buildDeleteRequestOptions(req, endpoint),
151 requestCallback(resolve, reject)
152 );
153 });
154 }
155
156 function handleMockResponse(req, success, statusCode, data, delay) {
157 delay = delay || 0;
158 return new Promise(function(resolve, reject) {
159 if (success) {
160 resolve_with_delay(resolve, { statusCode: statusCode, data: data }, delay)
161 } else { reject({ statusCode: statusCode, data: data }); }
162 });
163 }
164
165
166 function handleReject(req, statusCode, message) {
167 return new Promise(function(resolve, reject) {
168 reject({ statusCode: statusCode, data: message});
169 })
170 }
171
172 /**
173 * Calllback function to parse the response body into an object and
174 * remove the restconf top level key if it is present.
175 */
176 function transformLoggingRootResponseCallback(responseBody) {
177 var data = JSON.parse(responseBody);
178 if (data['rwlog-mgmt:logging']) {
179 data = data['rwlog-mgmt:logging'];
180 }
181 return data;
182 }
183
184
185 /**
186 * Debug function
187 */
188 function dumpLoggingConfig(data) {
189 console.log("dumpLoggingconfig");
190 var logConfig = data['lwlog-mgmt:logging'] || data;
191
192 console.log("keys=", Object.keys(logConfig));
193 console.log("stringify=", JSON.stringify(logConfig));
194 if (logConfig['default-severity']) {
195 logConfig['default-severity'].forEach(function(obj) {
196 console.log(obj);
197 })
198 }
199 if (logConfig['sink']) {
200 console.log('sink=', JSON.stringify(logConfig['sink']));
201 }
202 if (logConfig['console']) {
203 console.log('console=', JSON.stringify(logConfig['console']));
204 }
205 if (logConfig['deny']) {
206 console.log('deny=', JSON.stringify(logConfig['deny']));
207 }
208 }
209
210 // Aggregate calls
211
212 /**
213 * This method should fill out the full data set
214 */
215 Aggregate.get = function(req) {
216 // get config data
217 // get operational data
218 //massage them
219 var configData = Config.get(req);
220 var operationalData = Operational.get(req);
221
222 return new Promise(function(resolve, reject) {
223 Promise.all([configData, operationalData]).then(function(resolves) {
224 //console.log("Resolved all request promises (config, operational logging data)");
225 // TODO: Make sure the statusCodes for each resulves is 200
226 var decoder = new transforms.LoggingConfigDecoder();
227 resolve({
228 statusCode: 200,
229 data: decoder.decode(resolves[0], resolves[1])
230 });
231
232 }).catch(function(error) {
233 console.log("Logging: Aggregate.get error: ", error);
234 reject({
235 statusCode: 404,
236 errorMessage: error
237 })
238 })
239 });
240 }
241
242 /**
243 * This method expects the full data set (keys and values) for the logging
244 * config to replace the existing logging config
245 */
246 Aggregate.set = function(req) {
247 // NOTE: Left some debugging code remarked out
248
249 //console.log("Logging Aggregate.set called");
250 //console.log("data=", req.body);
251
252 // Do nothing to test delay in response
253 var encoder = new transforms.LoggingConfigEncoder();
254 var data = encoder.encode(req.body);
255 // console.log("Aggregate.set. encoded data=");
256 // console.log(data);
257 // dumpLoggingConfig(data);
258 let setData = {
259 'rwlog-mgmt:logging' : data
260 }
261 return handlePutRequest(req, APIVersion + '/api/config/logging', setData);
262 // if (this.mockResponse['set']) {
263 // return handleMockResponse(req, true, 201, data, delay=100);
264 // }
265 }
266
267
268
269
270
271 // Config calls
272
273 /**
274 * Get all currently set logging config data
275 */
276 Config.get = function(req) {
277 return handleGetRequest(req, APIVersion + '/api/config/logging?deep',
278 transformLoggingRootResponseCallback
279 );
280 }
281
282 /**
283 * Top level put method. Restconf cannot currently handle a global put on
284 * logging, so this method is currently for testing
285 */
286 Config.set = function(req) {
287 return handlePutRequest(req, APIVersion + '/api/config/logging');
288 }
289
290
291 Config.setConsole = function(req) {
292 return handlePutRequest(req, APIVersion + '/api/config/logging/console');
293 }
294
295 Config.setFilter = function(req) {
296 return handlePutRequest(req, APIVersion + '/api/config/logging/console/filter');
297 }
298
299 Config.setDefaultSeverity = function(req) {
300 // TODO: verify there is one key at root of data: 'default-severity'
301 // OR just filter on the request body
302 return handlePutRequest(req, APIVersion + '/api/config/logging/');
303 }
304
305 Config.deleteDefaultSeverity = function(req) {
306 // TODO: verify there is one key at root of data: 'default-severity'
307 // OR just filter on the request body
308 var Categories = req.body['default-severity'];
309 return new Promise(function(resolve, reject) {
310 var promises = Categories.map(function(c) {
311 return handleDeleteRequest(req, APIVersion + '/api/config/logging/default-severity/' + c.category);
312 });
313 return Promise.all(promises).then(
314 function(data) {
315 resolve(data[0]);
316 },
317 function(data) {
318 reject(data);
319 }
320 )
321 })
322
323 }
324
325 // NOTE: In rel_4.3 we are going to affect syslog sink category by default
326
327 Config.setDefaultSyslogSeverity = function(req) {
328 // TODO: verify there is one key at root of data: 'default-severity'
329 // OR just filter on the request body
330 return handlePutRequest(req, APIVersion + '/api/config/logging/sink/syslog');
331 }
332
333 Config.deleteDefaultSyslogSeverity = function(req) {
334 // TODO: verify there is one key at root of data: 'default-severity'
335 // OR just filter on the request body
336 var Categories = req.params.nulledCategories.split(',');
337 var promises = [];
338 return new Promise(function(resolve, reject) {
339 promises.concat(Categories.map(function(categoryName) {
340 return handleDeleteRequest(req, APIVersion + '/api/config/logging/sink/syslog/filter/category/' + categoryName);
341 }));
342 return Promise.all(promises).then(
343 function(data) {
344 resolve({statusCode: data[0].statusCode, data: data[0].data});
345 },
346 function(data) {
347 reject({statusCode: data[0].statusCode, data: data[0].data});
348 }
349 )
350 });
351 }
352
353 /*
354 get body of forms
355
356 {
357 "allowDuplicateEvents" : true
358 }
359 */
360
361 /**
362 * TODO: Repeat delete calls (when 'allowDuplicateEvents' value is false) cause
363 * a 404 error
364 * TODO: the call to handleDeleteRequest returns stringified data, but the PUT
365 * does not (This is the behavior we want)
366 *
367 * Improvement? Allos string representation of true/false
368 */
369 Config.setAllowDuplicateEvents = function(req) {
370 // TODO: verify there is one key at root of data: 'default-severity'
371 // OR just filter on the request body
372 console.log(req.body)
373 if (req.body.hasOwnProperty('allowDuplicateEvents')) {
374 if (req.body.allowDuplicateEvents.toUpperCase() == "TRUE") {
375 return handlePutRequest(req, '/api/config/logging/allow', {
376 "duplicate": "events"
377 });
378 } else { // false, remove entry from logging config
379 return handleDeleteRequest(req, '/api/config/logging/allow/duplicate');
380 }
381 } else {
382 return handleReject(statusCode=400,
383 data={
384 "message": 'Expected key, "allowDuplicateEvents" not found',
385 "original-request" : req.body
386 });
387 }
388 }
389
390 /*
391 "denyEvents": {
392 "eventIDs": [
393 1
394 ]
395 },
396 */
397
398 /*
399 "deny": {
400 "event": [
401 {
402 "event-Id": 1
403 }
404 ]
405 },
406 */
407
408 Config.setDenyEvents = function(req) {
409 var reqBody = {
410 deny: {
411 events: req.body.denyEvents.eventIDs.map(function(eventID) {
412 return { "event-Id": eventID };
413 })
414 }
415 };
416 return handlePutRequest(req, APIVersion + '/api/config/logging', reqBody);
417 }
418
419 Config.setSyslogViewer = function(req) {
420 // TODO: Verify structure of req.body
421 var reqBody = {
422 "syslog-viewer" : req.body['syslog-viewer']
423 }
424 return handlePutRequest(req, APIVersion + '/api/config/logging', reqBody);
425 }
426
427
428 // Operational calls
429
430 Operational.get = function(req) {
431 var APIVersion = '/v1'
432 return handleGetRequest(req, APIVersion + '/api/operational/logging?deep',
433 transformLoggingRootResponseCallback
434 );
435 }
436
437
438 /**
439 * Legacy call to get sys log viewer
440 */
441
442 SysLogViewer.get = function(req) {
443 console.log("\n***\n SysLogViewer.get called");
444 var api_server = req.query['api_server'];
445 return new Promise(function(resolve, reject) {
446 request({
447 uri: utils.confdPort(api_server) + APIVersion + '/api/config/logging/syslog-viewer',
448 method: 'GET',
449 headers: _.extend({},
450 constants.HTTP_HEADERS.accept.data,
451 {
452 'Authorization': req.get('Authorization')
453 }),
454 forever: foreverOn,
455 rejectUnauthorized: false
456 },
457 function(error, response, body) {
458 if(error) {
459 console.log('Logging.get failed. Error:', error);
460 reject({
461 statusCode: response ? response.statusCode : 404,
462 errorMessage: 'Issue retrieving syslog-viewer url'
463 });
464 } else {
465 var data;
466 try {
467 data = JSON.parse(response.body);
468 } catch (e) {
469 console.log('Logging.get failed while parsing response.body. Error:', e);
470 reject({
471 statusCode: 500,
472 errorMessage: 'Error parsing response.body during Logging.get'
473 });
474 }
475 resolve(data);
476 }
477 });
478 });
479 };
480
481 /**
482 * Test methods
483 */
484 Test.roundtrip = function(req) {
485 return new Promise(function(resolve, reject) {
486 Aggregate.get(req).then(function(result) {
487 var data = (new transforms.LoggingConfigEncoder()).encode(result.data);
488 resolve({
489 statusCode: 200,
490 data: {
491 'rwlog-mgmt:logging': data
492 }
493 });
494 }, function(err) {
495 console.log('Test.get error:', err);
496 reject({
497 statusCode: 500,
498 errorMessage: err
499 });
500 });
501 });
502 }
503
504 module.exports = {
505 aggregate: Aggregate,
506 config : Config,
507 operational: Operational,
508 sysLogViewer: SysLogViewer,
509 test: Test
510 }