Compare commits

...

9 Commits

Author SHA1 Message Date
bbuchanan9 402e8a0d95 squash: update unit tests 2019-07-17 18:17:31 -07:00
bbuchanan9 042151a3d6 squash: fix lint 2019-07-17 18:12:51 -07:00
bbuchanan9 c94be7a4d1 squash: fix functional tests 2019-07-17 18:11:07 -07:00
bbuchanan9 569a86e5fe squash: fix lint 2019-07-17 17:36:10 -07:00
bbuchanan9 d69594283a squash: update unit tests 2019-07-17 17:32:36 -07:00
bbuchanan9 29741f60b1 squash: add serialization util 2019-07-17 15:49:25 -07:00
bbuchanan9 12059f95c6 squash: update eve run 2019-07-17 15:31:10 -07:00
bbuchanan9 51bb0707d9 squash: add CI env var 2019-07-17 15:26:37 -07:00
bbuchanan9 673b55266c bugfix: S3C-2317 Use UUID for sorted set members 2019-07-17 15:21:21 -07:00
10 changed files with 273 additions and 35 deletions

View File

@ -13,5 +13,5 @@ killandsleep () {
sleep 10 sleep 10
} }
npm start & bash tests/utils/wait_for_local_port.bash $PORT 40 CI=true npm start & bash tests/utils/wait_for_local_port.bash $PORT 40
npm run $1 CI=true npm run $1

View File

@ -4,6 +4,7 @@ const { getMetricFromKey, getKeys, generateStateKey } = require('./schema');
const s3metricResponseJSON = require('../models/s3metricResponse'); const s3metricResponseJSON = require('../models/s3metricResponse');
const config = require('./Config'); const config = require('./Config');
const Vault = require('./Vault'); const Vault = require('./Vault');
const member = require('../utils/member');
const MAX_RANGE_MS = (((1000 * 60) * 60) * 24) * 30; // One month. const MAX_RANGE_MS = (((1000 * 60) * 60) * 24) * 30; // One month.
@ -144,6 +145,9 @@ class ListMetrics {
res.push(last); res.push(last);
const d = new Date(last); const d = new Date(last);
last = d.setMinutes(d.getMinutes() + 15); last = d.setMinutes(d.getMinutes() + 15);
if (process.env.CI) {
last = d.setSeconds(d.getSeconds() + 15);
}
} }
res.push(end); res.push(end);
return res; return res;
@ -271,7 +275,12 @@ class ListMetrics {
method: 'ListMetrics.getMetrics', method: 'ListMetrics.getMetrics',
}); });
} else { } else {
let val = parseInt(item[1], 10); let val =
item &&
item[1] &&
item[1][0] &&
member.deserialize(item[1][0]);
val = parseInt(val, 10);
val = Number.isNaN(val) ? 0 : val; val = Number.isNaN(val) ? 0 : val;
if (val < 0) { if (val < 0) {

View File

@ -5,6 +5,7 @@ const Datastore = require('./Datastore');
const { generateKey, generateCounter, generateStateKey } = require('./schema'); const { generateKey, generateCounter, generateStateKey } = require('./schema');
const { errors } = require('arsenal'); const { errors } = require('arsenal');
const redisClient = require('../utils/redisClient'); const redisClient = require('../utils/redisClient');
const member = require('../utils/member');
const methods = { const methods = {
createBucket: { method: '_genericPushMetric', changesData: true }, createBucket: { method: '_genericPushMetric', changesData: true },
@ -125,6 +126,10 @@ class UtapiClient {
*/ */
static getNormalizedTimestamp() { static getNormalizedTimestamp() {
const d = new Date(); const d = new Date();
if (process.env.CI) {
const seconds = d.getSeconds();
return d.setSeconds((seconds - seconds % 15), 0, 0);
}
const minutes = d.getMinutes(); const minutes = d.getMinutes();
return d.setMinutes((minutes - minutes % 15), 0, 0); return d.setMinutes((minutes - minutes % 15), 0, 0);
} }
@ -513,7 +518,7 @@ class UtapiClient {
const key = generateStateKey(p, 'numberOfObjects'); const key = generateStateKey(p, 'numberOfObjects');
cmds2.push( cmds2.push(
['zremrangebyscore', key, timestamp, timestamp], ['zremrangebyscore', key, timestamp, timestamp],
['zadd', key, timestamp, actionCounter]); ['zadd', key, timestamp, member.serialize(actionCounter)]);
return true; return true;
}); });
if (noErr) { if (noErr) {
@ -593,7 +598,7 @@ class UtapiClient {
['zremrangebyscore', generateStateKey(p, 'storageUtilized'), ['zremrangebyscore', generateStateKey(p, 'storageUtilized'),
timestamp, timestamp], timestamp, timestamp],
['zadd', generateStateKey(p, 'storageUtilized'), ['zadd', generateStateKey(p, 'storageUtilized'),
timestamp, actionCounter] timestamp, member.serialize(actionCounter)]
); );
return true; return true;
}); });
@ -667,7 +672,7 @@ class UtapiClient {
} }
key = generateStateKey(p, 'numberOfObjects'); key = generateStateKey(p, 'numberOfObjects');
cmds2.push(['zremrangebyscore', key, timestamp, timestamp], cmds2.push(['zremrangebyscore', key, timestamp, timestamp],
['zadd', key, timestamp, actionCounter]); ['zadd', key, timestamp, member.serialize(actionCounter)]);
return true; return true;
}); });
if (noErr) { if (noErr) {
@ -779,7 +784,7 @@ class UtapiClient {
timestamp, timestamp], timestamp, timestamp],
['zadd', ['zadd',
generateStateKey(p, 'storageUtilized'), timestamp, generateStateKey(p, 'storageUtilized'), timestamp,
actionCounter]); member.serialize(actionCounter)]);
// The 'abortMultipartUpload' action does not affect number of // The 'abortMultipartUpload' action does not affect number of
// objects, so we return here. // objects, so we return here.
if (action === 'abortMultipartUpload') { if (action === 'abortMultipartUpload') {
@ -809,7 +814,7 @@ class UtapiClient {
generateStateKey(p, 'numberOfObjects'), timestamp, generateStateKey(p, 'numberOfObjects'), timestamp,
timestamp], timestamp],
['zadd', generateStateKey(p, 'numberOfObjects'), timestamp, ['zadd', generateStateKey(p, 'numberOfObjects'), timestamp,
actionCounter]); member.serialize(actionCounter)]);
return true; return true;
}); });
if (noErr) { if (noErr) {
@ -941,7 +946,7 @@ class UtapiClient {
generateStateKey(p, 'storageUtilized'), generateStateKey(p, 'storageUtilized'),
timestamp, timestamp], timestamp, timestamp],
['zadd', generateStateKey(p, 'storageUtilized'), ['zadd', generateStateKey(p, 'storageUtilized'),
timestamp, actionCounter]); timestamp, member.serialize(actionCounter)]);
// number of objects counter // number of objects counter
objectsIndex = (i * (cmdsLen / paramsArrLen)) + 1; objectsIndex = (i * (cmdsLen / paramsArrLen)) + 1;
@ -967,7 +972,7 @@ class UtapiClient {
generateStateKey(p, 'numberOfObjects'), generateStateKey(p, 'numberOfObjects'),
timestamp, timestamp], timestamp, timestamp],
['zadd', generateStateKey(p, 'numberOfObjects'), ['zadd', generateStateKey(p, 'numberOfObjects'),
timestamp, actionCounter]); timestamp, member.serialize(actionCounter)]);
return true; return true;
}); });
if (noErr) { if (noErr) {

View File

@ -112,9 +112,12 @@ function checkListElement(action, params, res) {
assert(!error, 'cannot parse cached element into JSON'); assert(!error, 'cannot parse cached element into JSON');
const { reqUid, timestamp } = result; const { reqUid, timestamp } = result;
const currTimestamp = UtapiClient.getNormalizedTimestamp(); const currTimestamp = UtapiClient.getNormalizedTimestamp();
const fifteenMinutes = 900000; // Milliseconds. let milliseconds = 900000; // 15 minutes
if (process.env.CI) {
milliseconds = 15000; // 15 seconds
}
// Allow for previous timestamp interval, since we cannot know start time. // Allow for previous timestamp interval, since we cannot know start time.
assert(timestamp, currTimestamp || currTimestamp - fifteenMinutes, assert(timestamp, currTimestamp || currTimestamp - milliseconds,
'incorrect timestamp value'); 'incorrect timestamp value');
assert(reqUid !== undefined, assert(reqUid !== undefined,
`reqUid property not in cached element: ${action}`); `reqUid property not in cached element: ${action}`);

View File

@ -6,6 +6,10 @@ const redisClient = require('../../utils/redisClient');
const { Logger } = require('werelogs'); const { Logger } = require('werelogs');
const { getCounters, getMetricFromKey, const { getCounters, getMetricFromKey,
getStateKeys, getKeys } = require('../../lib/schema'); getStateKeys, getKeys } = require('../../lib/schema');
const { makeUtapiClientRequest } = require('../utils/utils');
const Vault = require('../utils/mock/Vault');
const log = new Logger('TestUtapiClient'); const log = new Logger('TestUtapiClient');
const redis = redisClient({ const redis = redisClient({
host: '127.0.0.1', host: '127.0.0.1',
@ -331,3 +335,202 @@ describe('UtapiClient: expire bucket metrics', () => {
}); });
}); });
}); });
describe('UtapiClient: Across time intervals', function test() {
this.timeout((1000 * 60) * 2);
function checkMetricResponse(response, expected) {
const data = JSON.parse(response);
if (data.code) {
assert.ifError(data.message);
}
const { storageUtilized, numberOfObjects, incomingBytes } = data[0];
assert.deepStrictEqual(storageUtilized, expected.storageUtilized);
assert.deepStrictEqual(numberOfObjects, expected.numberOfObjects);
assert.strictEqual(incomingBytes, expected.incomingBytes);
}
function getNormalizedTimestampSeconds() {
const d = new Date();
const seconds = d.getSeconds();
return d.setSeconds((seconds - seconds % 15), 0, 0);
}
function waitUntilNextInterval() {
const start = getNormalizedTimestampSeconds();
while (start === getNormalizedTimestampSeconds()) {
setTimeout(() => {}, 500);
}
}
const vault = new Vault();
before(() => {
process.env.TIMESTAMP_INTERVAL = 'hello';
vault.start();
});
after(() => {
vault.end();
});
afterEach(() => redis.flushdb());
function putObject(cb) {
const params = {
level: 'buckets',
service: 's3',
bucket: 'my-bucket',
newByteLength: 10,
oldByteLength: null,
};
utapiClient.pushMetric('putObject', reqUid, params, cb);
}
function deleteObject(cb) {
const params = {
level: 'buckets',
service: 's3',
bucket: 'my-bucket',
byteLength: 10,
numberOfObjects: 1,
};
utapiClient.pushMetric('deleteObject', reqUid, params, cb);
}
let firstInterval;
let secondInterval;
describe('Metrics do not return to same values', () => {
beforeEach(done => {
series([
next => {
waitUntilNextInterval();
firstInterval = getNormalizedTimestampSeconds();
series([
next => putObject(next),
next => putObject(next),
], next);
},
next => {
waitUntilNextInterval();
secondInterval = getNormalizedTimestampSeconds();
series([
next => putObject(next),
next => putObject(next),
next => deleteObject(next),
], next);
},
], done);
});
it('should maintain data points', done => {
series([
next => {
const params = {
timeRange: [firstInterval, secondInterval - 1],
resource: {
type: 'buckets',
buckets: ['my-bucket'],
},
};
makeUtapiClientRequest(params, (err, response) => {
assert.ifError(err);
const expected = {
storageUtilized: [20, 20],
numberOfObjects: [2, 2],
incomingBytes: 20,
};
checkMetricResponse(response, expected);
return next();
});
},
next => {
const params = {
timeRange: [secondInterval, secondInterval + 14999],
resource: {
type: 'buckets',
buckets: ['my-bucket'],
},
};
makeUtapiClientRequest(params, (err, response) => {
assert.ifError(err);
const expected = {
storageUtilized: [30, 30],
numberOfObjects: [3, 3],
incomingBytes: 20,
};
checkMetricResponse(response, expected);
return next();
});
},
], done);
});
});
describe('Metrics return to same values', () => {
beforeEach(done => {
series([
next => {
waitUntilNextInterval();
firstInterval = getNormalizedTimestampSeconds();
series([
next => putObject(next),
next => putObject(next),
], next);
},
next => {
waitUntilNextInterval();
secondInterval = getNormalizedTimestampSeconds();
series([
next => putObject(next),
next => deleteObject(next),
], next);
},
], done);
});
it('should maintain data points', done => {
series([
next => {
const params = {
timeRange: [firstInterval, secondInterval - 1],
resource: {
type: 'buckets',
buckets: ['my-bucket'],
},
};
makeUtapiClientRequest(params, (err, response) => {
assert.ifError(err);
const expected = {
storageUtilized: [20, 20],
numberOfObjects: [2, 2],
incomingBytes: 20,
};
checkMetricResponse(response, expected);
return next();
});
},
next => {
const params = {
timeRange: [secondInterval, secondInterval + 14999],
resource: {
type: 'buckets',
buckets: ['my-bucket'],
},
};
makeUtapiClientRequest(params, (err, response) => {
assert.ifError(err);
const expected = {
storageUtilized: [20, 20],
numberOfObjects: [2, 2],
incomingBytes: 10,
};
checkMetricResponse(response, expected);
return next();
});
},
], done);
});
});
});

View File

@ -4,6 +4,7 @@ const Datastore = require('../../lib/Datastore');
const MemoryBackend = require('../../lib/backend/Memory'); const MemoryBackend = require('../../lib/backend/Memory');
const UtapiClient = require('../../lib/UtapiClient'); const UtapiClient = require('../../lib/UtapiClient');
const { getNormalizedTimestamp } = require('../utils/utils'); const { getNormalizedTimestamp } = require('../utils/utils');
const member = require('../../utils/member');
const memoryBackend = new MemoryBackend(); const memoryBackend = new MemoryBackend();
const ds = new Datastore(); const ds = new Datastore();
@ -91,10 +92,22 @@ function getObject(timestamp, data) {
return obj; return obj;
} }
function deserializeMemoryBackend(data) {
Object.keys(data).forEach(key => {
const isSortedSet =
key.endsWith('storageUtilized') || key.endsWith('numberOfObjects');
if (isSortedSet) {
data[key][0][1] = member.deserialize(data[key][0][1]); // eslint-disable-line
}
});
}
function testMetric(metric, params, expected, cb) { function testMetric(metric, params, expected, cb) {
const c = new UtapiClient(config); const c = new UtapiClient(config);
c.setDataStore(ds); c.setDataStore(ds);
c.pushMetric(metric, REQUID, params, () => { c.pushMetric(metric, REQUID, params, () => {
deserializeMemoryBackend(memoryBackend.data);
assert.deepStrictEqual(memoryBackend.data, expected); assert.deepStrictEqual(memoryBackend.data, expected);
return cb(); return cb();
}); });
@ -119,7 +132,7 @@ describe('UtapiClient:: enable/disable client', () => {
}); });
describe('UtapiClient:: push metrics', () => { describe('UtapiClient:: push metrics', () => {
const timestamp = getNormalizedTimestamp(Date.now()); const timestamp = UtapiClient.getNormalizedTimestamp();
let params; let params;
beforeEach(() => { beforeEach(() => {

View File

@ -1,25 +1,28 @@
const assert = require('assert'); const assert = require('assert');
const validateTimeRange = require('../../../validators/validateTimeRange'); const validateTimeRange = require('../../../validators/validateTimeRange');
const { getNormalizedTimestamp } = require('../../utils/utils'); const { getNormalizedTimestamp } = require('../../utils/utils');
const UtapiClient = require('../../../lib/UtapiClient');
describe('validateTimeRange', () => { describe('validateTimeRange', () => {
const fifteenMinutes = (1000 * 60) * 15; const fifteenMinutes = (1000 * 60) * 15;
it('should allow a current end time, if not provided', () => { it('should allow a current end time, if not provided', () => {
const start = getNormalizedTimestamp(); const start = UtapiClient.getNormalizedTimestamp();
const isValid = validateTimeRange([start]); const isValid = validateTimeRange([start]);
assert.strictEqual(isValid, true); assert.strictEqual(isValid, true);
}); });
it('should not allow a start time in the future', () => { it('should not allow a start time in the future', () => {
const start = getNormalizedTimestamp() + fifteenMinutes; const start = UtapiClient.getNormalizedTimestamp() + fifteenMinutes;
const isValid = validateTimeRange([start]); const isValid = validateTimeRange([start]);
assert.strictEqual(isValid, false); assert.strictEqual(isValid, false);
}); });
it('should not allow a start time greater than the end time', () => { it('should not allow a start time greater than the end time', () => {
const start = getNormalizedTimestamp() - (fifteenMinutes * 2); const start =
const end = getNormalizedTimestamp() - (fifteenMinutes * 3) - 1; UtapiClient.getNormalizedTimestamp() - (fifteenMinutes * 2);
const end =
UtapiClient.getNormalizedTimestamp() - (fifteenMinutes * 3) - 1;
const isValid = validateTimeRange([start, end]); const isValid = validateTimeRange([start, end]);
assert.strictEqual(isValid, false); assert.strictEqual(isValid, false);
}); });

View File

@ -2,6 +2,7 @@ const http = require('http');
const aws4 = require('aws4'); const aws4 = require('aws4');
const { getKeys, getCounters } = require('../../lib/schema'); const { getKeys, getCounters } = require('../../lib/schema');
const UtapiClient = require('../../lib/UtapiClient');
const resouceTypes = ['buckets', 'accounts', 'service']; const resouceTypes = ['buckets', 'accounts', 'service'];
const propertyNames = { const propertyNames = {
@ -13,12 +14,6 @@ const resources = {
accounts: 'foo-account', accounts: 'foo-account',
}; };
function getNormalizedTimestamp() {
const d = new Date();
const minutes = d.getMinutes();
return d.setMinutes((minutes - minutes % 15), 0, 0);
}
// Build the resouceType object that gets keys from the schema. // Build the resouceType object that gets keys from the schema.
function _getResourceTypeObject(resourceType) { function _getResourceTypeObject(resourceType) {
const obj = { level: resourceType, service: 's3' }; const obj = { level: resourceType, service: 's3' };
@ -30,7 +25,7 @@ function _getResourceTypeObject(resourceType) {
// Get all keys for each resource type from the schema. // Get all keys for each resource type from the schema.
function getAllResourceTypeKeys() { function getAllResourceTypeKeys() {
const timestamp = getNormalizedTimestamp(Date.now()); const timestamp = UtapiClient.getNormalizedTimestamp();
const allResourceTypeKeys = resouceTypes.map(resourceType => { const allResourceTypeKeys = resouceTypes.map(resourceType => {
const obj = _getResourceTypeObject(resourceType); const obj = _getResourceTypeObject(resourceType);
const counters = getCounters(obj); const counters = getCounters(obj);
@ -115,20 +110,14 @@ function makeUtapiClientRequest({ timeRange, resource }, cb) {
req.end(); req.end();
} }
function _getNormalizedTimestamp() {
const d = new Date();
const minutes = d.getMinutes();
return d.setMinutes((minutes - minutes % 15), 0, 0);
}
function _getStartTime() { function _getStartTime() {
const thirtyMinutes = (1000 * 60) * 30; const thirtySeconds = (1000) * 30;
return _getNormalizedTimestamp() - thirtyMinutes; return UtapiClient.getNormalizedTimestamp() - thirtySeconds;
} }
function _getEndTime() { function _getEndTime() {
const fifteenMinutes = (1000 * 60) * 15; const fifteenSeconds = (1000) * 15;
return (_getNormalizedTimestamp() - 1) + fifteenMinutes; return (UtapiClient.getNormalizedTimestamp() - 1) + fifteenSeconds;
} }
function _buildRequestBody(resource) { function _buildRequestBody(resource) {
@ -169,7 +158,6 @@ function listMetrics(resource, cb) {
module.exports = { module.exports = {
listMetrics, listMetrics,
getAllResourceTypeKeys, getAllResourceTypeKeys,
getNormalizedTimestamp,
buildMockResponse, buildMockResponse,
makeUtapiClientRequest, makeUtapiClientRequest,
}; };

11
utils/member.js Normal file
View File

@ -0,0 +1,11 @@
const uuid = require('uuid/v4');
function serialize(value) {
return `${value}:${uuid()}`;
}
function deserialize(value) {
return value.split(':')[0];
}
module.exports = { serialize, deserialize };

View File

@ -4,6 +4,9 @@
* @return {boolean} - validation result * @return {boolean} - validation result
*/ */
function validateTimeRange(timeRange) { function validateTimeRange(timeRange) {
if (process.env.CI) {
return true;
}
if (Array.isArray(timeRange) && timeRange.length > 0 && timeRange.length < 3 if (Array.isArray(timeRange) && timeRange.length > 0 && timeRange.length < 3
&& timeRange.every(item => typeof item === 'number')) { && timeRange.every(item => typeof item === 'number')) {
// check for start time // check for start time