-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9a3d25f
commit c8e9147
Showing
4 changed files
with
347 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/** | ||
* Copyright (c) 2017, salesforce.com, inc. | ||
* All rights reserved. | ||
* Licensed under the BSD 3-Clause license. | ||
* For full license text, see LICENSE.txt file in the repo root or | ||
* https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
|
||
/** | ||
* ./cache/sampleStoreTimeout.js | ||
* | ||
* Timeout samples | ||
*/ | ||
'use strict'; // eslint-disable-line strict | ||
const sampleStore = require('./sampleStore'); | ||
const redisClient = require('./redisCache').client.sampleStore; | ||
const isTimedOut = require('../db/helpers/sampleUtils').isTimedOut; | ||
const constants = require('../api/v1/constants'); | ||
const ONE = 1; | ||
const TWO = 2; | ||
|
||
module.exports = { | ||
|
||
/** | ||
* Invalidates samples which were last updated before the "timeout" specified | ||
* by the aspect. Get all samples and corresponding aspects. If the sample | ||
* should be timed out, set sample value, status, previous status, status | ||
* changed at and updated at fields. | ||
* @param {Date} now - Date object | ||
* @returns {Promise} - Resolves to the number of evaluated and timed out | ||
* samples | ||
*/ | ||
doTimeout(now) { | ||
const curr = now || new Date(); | ||
let numberTimedOut = 0; | ||
let numberEvaluated = 0; | ||
|
||
return new Promise((resolve, reject) => { | ||
redisClient.smembersAsync(sampleStore.constants.indexKey.sample) | ||
.then((allSamples) => { | ||
const commands = []; | ||
const aspectType = sampleStore.constants.objectType.aspect; | ||
|
||
allSamples.forEach((sampKey) => { | ||
const aspectName = sampKey.split('|')[ONE]; | ||
commands.push(['hgetall', sampKey]); // get sample | ||
commands.push( | ||
['hgetall', sampleStore.toKey(aspectType, aspectName)] | ||
); | ||
}); | ||
|
||
return redisClient.batch(commands).execAsync(); | ||
}) | ||
.then((redisResponses) => { | ||
const samples = []; | ||
const aspects = []; | ||
const sampCmds = []; | ||
|
||
for (let num = 0; num < redisResponses.length; num += TWO) { | ||
samples.push(redisResponses[num]); | ||
aspects.push(redisResponses[num + ONE]); | ||
} | ||
|
||
for (let num = 0; num < samples.length; num++) { | ||
const samp = samples[num]; | ||
const asp = aspects[num]; | ||
const sampUpdDateTime = new Date(samp.updatedAt); | ||
if (asp && isTimedOut(asp.timeout, curr, sampUpdDateTime)) { | ||
const sampType = sampleStore.constants.objectType.sample; | ||
|
||
const objToUpdate = { | ||
value: constants.statuses.Timeout, | ||
status: constants.statuses.Timeout, | ||
previousStatus: samp.status, | ||
statusChangedAt: new Date().toString(), | ||
updatedAt: new Date().toString(), | ||
}; | ||
sampCmds.push([ | ||
'hmset', | ||
sampleStore.toKey(sampType, samp.name), | ||
objToUpdate, | ||
]); | ||
numberTimedOut++; | ||
} | ||
} | ||
|
||
numberEvaluated = samples.length; | ||
return redisClient.batch(sampCmds).execAsync(); | ||
}) | ||
.then(() => resolve({ numberEvaluated, numberTimedOut })) | ||
.catch(reject); | ||
}); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
/** | ||
* Copyright (c) 2017, salesforce.com, inc. | ||
* All rights reserved. | ||
* Licensed under the BSD 3-Clause license. | ||
* For full license text, see LICENSE.txt file in the repo root or | ||
* https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
|
||
/** | ||
* tests/cache/models/samples/timeout.js | ||
*/ | ||
'use strict'; // eslint-disable-line strict | ||
|
||
const tu = require('../../../testUtils'); | ||
const rtu = require('../redisTestUtil'); | ||
const samstoinit = require('../../../../cache/sampleStoreInit'); | ||
const doTimeout = require('../../../../cache/sampleStoreTimeout').doTimeout; | ||
const redisClient = require('../../../../cache/redisCache').client.sampleStore; | ||
const expect = require('chai').expect; | ||
const Sample = tu.db.Sample; | ||
const Aspect = tu.db.Aspect; | ||
const Subject = tu.db.Subject; | ||
|
||
describe(`api::cache::timeout`, () => { | ||
let updatedAt; | ||
const defaultForStatus = 'Timeout'; | ||
const twentyFourhours = 24; | ||
const hundredDays = 100; | ||
const tenSeconds = 10; | ||
const fiveMinutes = 5; | ||
|
||
before(() => tu.toggleOverride('enableRedisSampleStore', true)); | ||
beforeEach((done) => { | ||
Aspect.create({ | ||
isPublished: true, | ||
name: `${tu.namePrefix}OneSecond`, | ||
timeout: '1s', | ||
valueType: 'NUMERIC', | ||
criticalRange: [0, 0], | ||
warningRange: [1, 1], | ||
infoRange: [2, 2], | ||
okRange: [3, 3], | ||
}) | ||
.then(() => Aspect.create({ | ||
isPublished: true, | ||
name: `${tu.namePrefix}TwoMinutes`, | ||
timeout: '2m', | ||
valueType: 'NUMERIC', | ||
criticalRange: [0, 0], | ||
warningRange: [1, 1], | ||
infoRange: [2, 2], | ||
okRange: [3, 3], | ||
})) | ||
.then(() => Aspect.create({ | ||
isPublished: true, | ||
name: `${tu.namePrefix}ThreeHours`, | ||
timeout: '3H', | ||
valueType: 'NUMERIC', | ||
criticalRange: [0, 0], | ||
warningRange: [1, 1], | ||
infoRange: [2, 2], | ||
okRange: [3, 3], | ||
})) | ||
.then(() => Aspect.create({ | ||
isPublished: true, | ||
name: `${tu.namePrefix}NinetyDays`, | ||
timeout: '90D', | ||
valueType: 'NUMERIC', | ||
criticalRange: [0, 0], | ||
warningRange: [1, 1], | ||
infoRange: [2, 2], | ||
okRange: [3, 3], | ||
})) | ||
.then(() => Subject.create({ | ||
isPublished: true, | ||
name: `${tu.namePrefix}Subject`, | ||
})) | ||
.then(() => Sample.bulkUpsertByName([ | ||
{ name: `${tu.namePrefix}Subject|${tu.namePrefix}OneSecond`, value: 1 }, | ||
{ name: `${tu.namePrefix}Subject|${tu.namePrefix}TwoMinutes`, value: 1 }, | ||
{ name: `${tu.namePrefix}Subject|${tu.namePrefix}ThreeHours`, value: 2 }, | ||
{ name: `${tu.namePrefix}Subject|${tu.namePrefix}NinetyDays`, value: 3 }, | ||
])) | ||
.then(() => Sample.findAll({ | ||
attributes: ['name', 'updatedAt'], | ||
where: { | ||
name: { | ||
$ilike: `${tu.namePrefix}Subject|%`, | ||
}, | ||
}, | ||
}) | ||
.each((s) => { | ||
updatedAt = s.updatedAt; | ||
})) | ||
.then(() => samstoinit.eradicate()) | ||
.then(() => samstoinit.init()) | ||
.then(() => done()) | ||
.catch(done); | ||
}); | ||
|
||
afterEach(rtu.forceDelete); | ||
after(() => tu.toggleOverride('enableRedisSampleStore', false)); | ||
|
||
it('simulate 100 days in the future', (done) => { | ||
const mockUpdatedAt = updatedAt; | ||
mockUpdatedAt.setHours(updatedAt.getHours() + | ||
(twentyFourhours * hundredDays)); | ||
doTimeout(mockUpdatedAt) | ||
.then((res) => { | ||
expect(res).to.eql({ numberEvaluated: 4, numberTimedOut: 4 }); | ||
}) | ||
.then(() => redisClient.keysAsync( | ||
`samsto:sample:${tu.namePrefix}Subject|*`.toLowerCase()) | ||
) | ||
.then((sNames) => { | ||
const commands = []; | ||
sNames.forEach((s) => { | ||
commands.push(['hgetall', s]); | ||
}); | ||
return redisClient.batch(commands).execAsync(); | ||
}) | ||
.then((samples) => { | ||
samples.forEach((s) => { | ||
expect(s.status).to.equal(defaultForStatus); | ||
}); | ||
done(); | ||
}) | ||
.catch(done); | ||
}); | ||
|
||
it('simulate 1 day in the future', (done) => { | ||
const mockUpdatedAt = updatedAt; | ||
mockUpdatedAt.setHours(updatedAt.getHours() + twentyFourhours); | ||
doTimeout(mockUpdatedAt) | ||
.then((res) => { | ||
expect(res).to.eql({ numberEvaluated: 4, numberTimedOut: 3 }); | ||
}) | ||
.then(() => redisClient.keysAsync( | ||
`samsto:sample:${tu.namePrefix}Subject|*`.toLowerCase()) | ||
) | ||
.then((sNames) => { | ||
const commands = []; | ||
sNames.forEach((s) => { | ||
commands.push(['hgetall', s]); | ||
}); | ||
return redisClient.batch(commands).execAsync(); | ||
}) | ||
.then((samples) => { | ||
samples.forEach((s) => { | ||
switch (s.name) { | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}OneSecond`: | ||
expect(s.status).to.equal(defaultForStatus); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}TwoMinutes`: | ||
expect(s.status).to.equal(defaultForStatus); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}ThreeHours`: | ||
expect(s.status).to.equal(defaultForStatus); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}NinetyDays`: | ||
expect(s.status).to.not.equal(defaultForStatus); | ||
break; | ||
} | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
}); | ||
|
||
it('simulate 5 minutes in the future', (done) => { | ||
const mockUpdatedAt = updatedAt; | ||
mockUpdatedAt.setMinutes(updatedAt.getMinutes() + fiveMinutes); | ||
doTimeout(mockUpdatedAt) | ||
.then((res) => { | ||
expect(res).to.eql({ numberEvaluated: 4, numberTimedOut: 2 }); | ||
}) | ||
.then(() => redisClient.keysAsync( | ||
`samsto:sample:${tu.namePrefix}Subject|*`.toLowerCase()) | ||
) | ||
.then((sNames) => { | ||
const commands = []; | ||
sNames.forEach((s) => { | ||
commands.push(['hgetall', s]); | ||
}); | ||
return redisClient.batch(commands).execAsync(); | ||
}) | ||
.then((samples) => { | ||
samples.forEach((s) => { | ||
switch (s.name) { | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}OneSecond`: | ||
expect(s.status).to.equal(defaultForStatus); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}TwoMinutes`: | ||
expect(s.status).to.equal(defaultForStatus); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}ThreeHours`: | ||
expect(s.status).to.not.equal(null); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}NinetyDays`: | ||
expect(s.status).to.not.equal(null); | ||
break; | ||
} | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
}); | ||
|
||
it('simulate 10 seconds in the past', (done) => { | ||
const mockUpdatedAt = updatedAt; | ||
mockUpdatedAt.setSeconds(updatedAt.getSeconds() - tenSeconds); | ||
doTimeout(mockUpdatedAt) | ||
.then((res) => { | ||
expect(res).to.eql({ numberEvaluated: 4, numberTimedOut: 0 }); | ||
}) | ||
.then(() => redisClient.keysAsync( | ||
`samsto:sample:${tu.namePrefix}Subject|*`.toLowerCase()) | ||
) | ||
.then((sNames) => { | ||
const commands = []; | ||
sNames.forEach((s) => { | ||
commands.push(['hgetall', s]); | ||
}); | ||
return redisClient.batch(commands).execAsync(); | ||
}) | ||
.then((samples) => { | ||
samples.forEach((s) => { | ||
switch (s.name) { | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}OneSecond`: | ||
expect(s.status).to.not.equal(null); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}TwoMinutes`: | ||
expect(s.status).to.not.equal(null); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}ThreeHours`: | ||
expect(s.status).to.not.equal(null); | ||
break; | ||
case `${tu.namePrefix}Subject|${tu.namePrefix}NinetyDays`: | ||
expect(s.status).to.not.equal(null); | ||
break; | ||
} | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
}); | ||
}); |