Skip to content
This repository was archived by the owner on Dec 14, 2022. It is now read-only.

Commit feb4094

Browse files
author
Chris Wiechmann
committed
Added the option to negate the statusCode filter (e.g. !2xx)
#100
1 parent e4578be commit feb4094

File tree

3 files changed

+171
-36
lines changed

3 files changed

+171
-36
lines changed

apibuilder4elastic/custom_flow_nodes/api-builder-plugin-traffic-monitor-api-utils/src/actions.js

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async function handleFilterFields(parameters, options) {
4040
{ fieldName: 'remotePort', queryType: 'match', queryLocation: 'http.remotePort' },
4141
{ fieldName: 'remoteName', queryType: 'match', queryLocation: 'http.remoteName' },
4242
{ fieldName: 'localAddr', queryType: 'match', queryLocation: 'http.localAddr' },
43-
{ fieldName: 'status', queryType: 'match', queryLocation: 'http.status' },
43+
{ fieldName: 'status', queryType: 'match', queryLocation: addStatusFilter },
4444
{ fieldName: 'subject', queryType: 'match', queryLocation: 'http.authSubjectId' },
4545
{ fieldName: 'operation', queryType: 'match', queryLocation: 'serviceContext.method' },
4646
{ fieldName: 'localPort', queryType: 'match', queryLocation: 'http.localPort' },
@@ -71,7 +71,10 @@ async function handleFilterFields(parameters, options) {
7171

7272
var elasticSearchquery = { bool: { must: [] } };
7373

74-
var filters = [];
74+
var filters = {
75+
mustFilters: [],
76+
mustNotFilters: []
77+
};
7578

7679
// Iterate over all known filter fields (e.g. uri, wafstatus, ...)
7780
fields.map(function (entry) {
@@ -92,27 +95,36 @@ async function handleFilterFields(parameters, options) {
9295
var value = params.value[index];
9396
var filter = {};
9497
var filterQuery = {};
95-
// Example: filterQuery[http.uri] = /petstore/v2/pet/findByTag
96-
filterQuery[queryLocation] = { query: value, ...queryParams };
97-
// Add search params to the filterQuery if given
98-
/*if(queryParams) {
99-
filterQuery = {...queryParams, ...filterQuery };
100-
}*/
101-
// Example: filter[match] = { http.uri: /petstore/v2/pet/findByTag }
102-
filter[queryType] = filterQuery;
103-
// All filters are then pushed into the queryFilters array
104-
// Example: [ {"match":{"http.uri":"/petstore/v2/pet/findByTag"}} , {"match":{"http.method":"GET"}} ]
105-
filters.push(filter);
98+
if(typeof queryLocation == 'function') {
99+
queryLocation(filters, entry.fieldName, value, queryParams, params)
100+
} else {
101+
// Example: filterQuery[http.uri] = /petstore/v2/pet/findByTag
102+
filterQuery[queryLocation] = { query: value, ...queryParams };
103+
// Add search params to the filterQuery if given
104+
/*if(queryParams) {
105+
filterQuery = {...queryParams, ...filterQuery };
106+
}*/
107+
// Example: filter[match] = { http.uri: /petstore/v2/pet/findByTag }
108+
filter[queryType] = filterQuery;
109+
// All filters are then pushed into the queryFilters array
110+
// Example: [ {"match":{"http.uri":"/petstore/v2/pet/findByTag"}} , {"match":{"http.method":"GET"}} ]
111+
filters.mustFilters.push(filter);
112+
}
106113
logger.debug(JSON.stringify(filter));
107114
}
108115
});
109116
});
110-
await addProtocolFilter(filters, params, logger);
111-
await addServiceIdFilter(filters, serviceID, logger);
112-
await addDurationFilter(filters, params, logger);
113-
await addAgoFilter(filters, params, logger);
114-
await addTimestampFilter(filters, params, logger);
115-
elasticSearchquery.bool.must = filters;
117+
await addProtocolFilter(filters.mustFilters, params, logger);
118+
await addServiceIdFilter(filters.mustFilters, serviceID, logger);
119+
await addDurationFilter(filters.mustFilters, params, logger);
120+
await addAgoFilter(filters.mustFilters, params, logger);
121+
await addTimestampFilter(filters.mustFilters, params, logger);
122+
if(filters.mustFilters.length != 0) {
123+
elasticSearchquery.bool.must = filters.mustFilters;
124+
}
125+
if(filters.mustNotFilters.length != 0) {
126+
elasticSearchquery.bool.must_not = filters.mustNotFilters;
127+
}
116128
logger.info(`Elasticsearch query: ${JSON.stringify(elasticSearchquery)}`);
117129
return elasticSearchquery;
118130
}
@@ -245,6 +257,35 @@ async function _addLegDetails(sourceLeg, resultLeg, correlationId, timestamp, lo
245257
}
246258
}
247259

260+
function addStatusFilter(filters, fieldName, statusCodeFilter, queryParams, params) {
261+
if (typeof statusCodeFilter == 'undefined') {
262+
return;
263+
}
264+
if(!statusCodeFilter.match(/^!?[0-9x]{3}$/)) { // Matches 200, !500, !2xx, 3xx
265+
throw new Error(`Unsupported status code filter: ${statusCodeFilter}`);
266+
}
267+
var filtersToUse;
268+
if(statusCodeFilter.startsWith("!")) {
269+
filtersToUse = filters.mustNotFilters;
270+
statusCodeFilter = statusCodeFilter.replace("!", "");
271+
} else {
272+
filtersToUse = filters.mustFilters;
273+
}
274+
var startValue;
275+
var endValue;
276+
// Direct filter such as 200 or !200
277+
if(statusCodeFilter.indexOf("x")==-1) {
278+
startValue = parseInt(statusCodeFilter);
279+
endValue = parseInt(statusCodeFilter);
280+
// A range filter such as 2xx or !2xx or even 40x or !40x
281+
} else {
282+
startValue = parseInt(statusCodeFilter.replace(/x/g, "0"));
283+
endValue = parseInt(statusCodeFilter.replace(/x/g, "9"));
284+
}
285+
filtersToUse.push({ "range": { "http.status": { "gte": startValue, "lte": endValue } } });
286+
return filters;
287+
}
288+
248289
async function addProtocolFilter(filters, params, logger) {
249290
/*
250291
* Default handling for some of the fields

apibuilder4elastic/custom_flow_nodes/api-builder-plugin-traffic-monitor-api-utils/test/testHandleFilterQueries.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,81 @@ describe('flow-node traffic-monitor-api-utils', () => {
114114
{"term": {"processInfo.serviceId": "instance-1"}}
115115
]}});
116116
});
117+
118+
it('should succeed with a direct status filter', async () => {
119+
const { value, output } = await flowNode.handleFilterFields({ params: { field: "status", value: "200" }, serviceID: "instance-1" });
120+
121+
expect(output).to.equal('next');
122+
expect(value).to.be.a('object');
123+
124+
expect(value).to.deep.equal({ "bool": { "must": [
125+
{ range: {"http.status": { "gte": 200, "lte": 200 }}},
126+
{ exists: { "field": "http"} },
127+
{ term: {"processInfo.serviceId": "instance-1"}}
128+
]}});
129+
});
130+
131+
it('should succeed with a 2xx status filter', async () => {
132+
const { value, output } = await flowNode.handleFilterFields({ params: { field: "status", value: "2xx" }, serviceID: "instance-1" });
133+
134+
expect(output).to.equal('next');
135+
expect(value).to.be.a('object');
136+
137+
expect(value).to.deep.equal({ "bool": { "must": [
138+
{ range: {"http.status": { "gte": 200, "lte": 299 }}},
139+
{ exists: { "field": "http"} },
140+
{ term: {"processInfo.serviceId": "instance-1"}}
141+
]}});
142+
});
143+
144+
it('should succeed with a negated direct 200 status filter', async () => {
145+
const { value, output } = await flowNode.handleFilterFields({ params: { field: "status", value: "!200" }, serviceID: "instance-1" });
146+
147+
expect(output).to.equal('next');
148+
expect(value).to.be.a('object');
149+
150+
expect(value).to.deep.equal({ "bool":
151+
{ "must": [
152+
{ exists: { "field": "http"} },
153+
{ term: {"processInfo.serviceId": "instance-1"}}
154+
],
155+
"must_not": [
156+
{ range: {"http.status": { "gte": 200, "lte": 200 }}},
157+
]}}
158+
);
159+
});
160+
161+
it('should succeed with a negated direct 200 status filter', async () => {
162+
const { value, output } = await flowNode.handleFilterFields({ params: { field: "status", value: "!2xx" }, serviceID: "instance-1" });
163+
164+
expect(output).to.equal('next');
165+
expect(value).to.be.a('object');
166+
167+
expect(value).to.deep.equal({ "bool":
168+
{ "must": [
169+
{ exists: { "field": "http"} },
170+
{ term: {"processInfo.serviceId": "instance-1"}}
171+
],
172+
"must_not": [
173+
{ range: {"http.status": { "gte": 200, "lte": 299 }}}
174+
]}
175+
});
176+
});
177+
178+
it('should fail with an invalid status code filter (too long)', async () => {
179+
const { value, output } = await flowNode.handleFilterFields({ params: { field: "status", value: "!2xx6" }, serviceID: "instance-1" });
180+
181+
expect(value).to.be.instanceOf(Error)
182+
.and.to.have.property('message', 'Unsupported status code filter: !2xx6');
183+
expect(output).to.equal('error');
184+
});
185+
186+
it('should fail with an invalid status code filter (invalid characters)', async () => {
187+
const { value, output } = await flowNode.handleFilterFields({ params: { field: "status", value: "!2ZZ" }, serviceID: "instance-1" });
188+
189+
expect(value).to.be.instanceOf(Error)
190+
.and.to.have.property('message', 'Unsupported status code filter: !2ZZ');
191+
expect(output).to.equal('error');
192+
});
117193
});
118194
});

apibuilder4elastic/test/trafficMonitorAPI/asAdmin/http/test_search_endpoint_AsAdmin.js

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,25 @@ describe('Endpoints', function () {
299299
expect(body.data[0].uri).to.equals('/favicon.ico');
300300
});
301301
});
302-
it('[Endpoint-0012] should return one entry with localadr 1.1.1.1', async () => {
302+
it('[Endpoint-0012] should return all entries not having a 200 status code', async () => {
303+
return await requestAsync({
304+
method: 'GET',
305+
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=status&value=!2xx`,
306+
headers: {
307+
'cookie': 'VIDUSR=Search-0011-DAVID-1597468226-Z+qdRW4rGZnwzQ==',
308+
'csrf-token': '04F9F07E59F588CDE469FC367A12ED3A4B845FDA9A9AE2D9A77686823067CDDC'
309+
},
310+
json: true
311+
}).then(({ response, body }) => {
312+
expect(response.statusCode).to.equal(200);
313+
expect(body).to.be.an('Object');
314+
expect(body).to.have.property('data');
315+
expect(body.data).to.have.lengthOf(1);
316+
expect(body.data[0].status).to.equals(404);
317+
expect(body.data[0].uri).to.equals('/favicon.ico');
318+
});
319+
});
320+
it('[Endpoint-0013] should return one entry with localadr 1.1.1.1', async () => {
303321
return await requestAsync({
304322
method: 'GET',
305323
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=localAddr&value=1.1.1.1`,
@@ -317,7 +335,7 @@ describe('Endpoints', function () {
317335
expect(body.data[0].uri).to.equals('/healthcheck');
318336
});
319337
});
320-
it('[Endpoint-0013] should return one entry with remoteName (remoteHost) TestHost', async () => {
338+
it('[Endpoint-0014] should return one entry with remoteName (remoteHost) TestHost', async () => {
321339
return await requestAsync({
322340
method: 'GET',
323341
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=remoteName&value=TestHost`,
@@ -335,7 +353,7 @@ describe('Endpoints', function () {
335353
expect(body.data[0].uri).to.equals('/healthcheck');
336354
});
337355
});
338-
it('[Endpoint-0014] should return one entry with remotePort 59641', async () => {
356+
it('[Endpoint-0015] should return one entry with remotePort 59641', async () => {
339357
return await requestAsync({
340358
method: 'GET',
341359
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=remotePort&value=59641`,
@@ -353,7 +371,7 @@ describe('Endpoints', function () {
353371
expect(body.data[0].uri).to.equals('/favicon.ico');
354372
});
355373
});
356-
it('[Endpoint-0015] should return one entry with service name Petstore HTTP', async () => {
374+
it('[Endpoint-0016] should return one entry with service name Petstore HTTP', async () => {
357375
return await requestAsync({
358376
method: 'GET',
359377
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=serviceName&value=Petstore%20HTTP`,
@@ -372,7 +390,7 @@ describe('Endpoints', function () {
372390
expect(body.data[1].uri).to.equals('/petstore/v2/pet/findByStatus');
373391
});
374392
});
375-
it('[Endpoint-0016] should return one entry WAF-Status 1', async () => {
393+
it('[Endpoint-0017] should return one entry WAF-Status 1', async () => {
376394
return await requestAsync({
377395
method: 'GET',
378396
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=wafStatus&value=1`,
@@ -390,7 +408,7 @@ describe('Endpoints', function () {
390408
expect(body.data[0].uri).to.equals('/favicon.ico');
391409
});
392410
});
393-
it('[Endpoint-0017] should return one entry with the given correlation id', async () => {
411+
it('[Endpoint-0018] should return one entry with the given correlation id', async () => {
394412
return await requestAsync({
395413
method: 'GET',
396414
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=correlationId&value=682c0f5fbe23dc8e1d80efe2`,
@@ -408,7 +426,7 @@ describe('Endpoints', function () {
408426
expect(body.data[0].serviceName).to.equals('Petstore');
409427
});
410428
});
411-
it('[Endpoint-0018] should return one entry with final status Error', async () => {
429+
it('[Endpoint-0019] should return one entry with final status Error', async () => {
412430
return await requestAsync({
413431
method: 'GET',
414432
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=finalStatus&value=Error`,
@@ -425,7 +443,7 @@ describe('Endpoints', function () {
425443
expect(body.data[0].uri).to.equals('/healthcheck');
426444
});
427445
});
428-
it('[Endpoint-0019] should return results with a wildcard path.', async () => {
446+
it('[Endpoint-0020] should return results with a wildcard path.', async () => {
429447
return await requestAsync({
430448
method: 'GET',
431449
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=uri&value=%2Fv2%2Fpet`,
@@ -443,7 +461,7 @@ describe('Endpoints', function () {
443461
expect(body.data[1].uri).to.equals('/petstore/v2/pet/findByStatus');
444462
});
445463
});
446-
it('[Endpoint-0020] Should return 1 entry in the last 10 minutes (ago=10m)', async () => {
464+
it('[Endpoint-0021] Should return 1 entry in the last 10 minutes (ago=10m)', async () => {
447465
return await requestAsync({
448466
method: 'GET',
449467
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?ago=10m`,
@@ -460,7 +478,7 @@ describe('Endpoints', function () {
460478
expect(body.data[0].uri).to.equals('/petstore/v2/pet/findByTag');
461479
});
462480
});
463-
it('[Endpoint-0021] Should return 2 entries in the last 30 minutes (ago=30m)', async () => {
481+
it('[Endpoint-0022] Should return 2 entries in the last 30 minutes (ago=30m)', async () => {
464482
return await requestAsync({
465483
method: 'GET',
466484
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?ago=30m`,
@@ -477,7 +495,7 @@ describe('Endpoints', function () {
477495
expect(body.data[0].uri).to.equals('/petstore/v2/pet/findByTag');
478496
});
479497
});
480-
it('[Endpoint-0022] Should return 4 entries in the last 2 hours (ago=120h)', async () => {
498+
it('[Endpoint-0023] Should return 4 entries in the last 2 hours (ago=120h)', async () => {
481499
return await requestAsync({
482500
method: 'GET',
483501
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?ago=2h`,
@@ -494,7 +512,7 @@ describe('Endpoints', function () {
494512
expect(body.data[0].uri).to.equals('/petstore/v2/pet/findByTag');
495513
});
496514
});
497-
it('[Endpoint-0023] Should include the V-Host value', async () => {
515+
it('[Endpoint-0024] Should include the V-Host value', async () => {
498516
return await requestAsync({
499517
method: 'GET',
500518
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=correlationId&value=7a240f5f0e21555d2d343482`,
@@ -509,7 +527,7 @@ describe('Endpoints', function () {
509527
expect(body.data[0].vhost).to.equal('api.customer.com:443', 'V-Host is not part of the result');
510528
});
511529
});
512-
it('[Endpoint-0024] Should not return anything when using the wrong request protocol.', async () => {
530+
it('[Endpoint-0025] Should not return anything when using the wrong request protocol.', async () => {
513531
return await requestAsync({
514532
method: 'GET',
515533
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?protocol=filetransfer`,
@@ -525,7 +543,7 @@ describe('Endpoints', function () {
525543
expect(body.data).to.have.lengthOf(0);
526544
});
527545
});
528-
it('[Endpoint-0025] Should return the OPTIONS request including the URI', async () => {
546+
it('[Endpoint-0026] Should return the OPTIONS request including the URI', async () => {
529547
return await requestAsync({
530548
method: 'GET',
531549
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-2/ops/search?protocol=http&field=method&value=OPTIONS`,
@@ -544,7 +562,7 @@ describe('Endpoints', function () {
544562
});
545563

546564
// See issue #52
547-
it('[Endpoint-0026] With query on v2/pet/findByTag should return only ONE API.', async () => {
565+
it('[Endpoint-0027] With query on v2/pet/findByTag should return only ONE API.', async () => {
548566
return await requestAsync({
549567
method: 'GET',
550568
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=uri&value=%2Fpetstore%2Fv2%2Fpet%2FfindByTag&field=method&value=GET`,
@@ -563,7 +581,7 @@ describe('Endpoints', function () {
563581
});
564582
});
565583

566-
it('[Endpoint-0027] Should ignore the region if null', async () => {
584+
it('[Endpoint-0028] Should ignore the region if null', async () => {
567585
return await requestAsync({
568586
method: 'GET',
569587
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=uri&value=%2Fpetstore%2Fv2%2Fpet%2FfindByTag&field=method&value=GET`,
@@ -583,7 +601,7 @@ describe('Endpoints', function () {
583601
});
584602
// For any reason, this test-case sometimes returns a result and sometimes not
585603
//
586-
it.skip('[Endpoint-0028] should return one entry with localport 8080 and a part of the original subject ID', async () => {
604+
it.skip('[Endpoint-0029] should return one entry with localport 8080 and a part of the original subject ID', async () => {
587605
return await requestAsync({
588606
method: 'GET',
589607
uri: `http://localhost:${server.apibuilder.port}/api/elk/v1/api/router/service/instance-1/ops/search?field=localPort&value=8080&field=subject&value=Chris`,

0 commit comments

Comments
 (0)