Skip to content

Commit

Permalink
pulled and fixed conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
kriscfoster committed Aug 17, 2017
2 parents 83b4ed7 + ac8db62 commit 22261d0
Show file tree
Hide file tree
Showing 364 changed files with 11,159 additions and 14,104 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ env:
- CXX=g++-4.8

addons:
postgresql: "9.4"
postgresql: "9.6"
apt:
sources:
- ubuntu-toolchain-r-test
Expand Down
9 changes: 9 additions & 0 deletions api/v1/apiErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ apiErrors.create({
defaultMessage: 'A subject may not be its own parent.',
});

apiErrors.create({
code: 11110,
status: 400,
name: 'DuplicateResourceError',
parent: apiErrors.ValidationError,
fields: [],
defaultMessage: 'You are not allowed to create a resource that already exists.',
});

// ----------------------------------------------------------------------------
// Not Found
// ----------------------------------------------------------------------------
Expand Down
17 changes: 13 additions & 4 deletions api/v1/controllers/samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,12 @@ module.exports = {
*send the upserted sample to the client by publishing it to the redis
*channel
*/
publisher.publishSample(samp, subHelper.model);
if (featureToggles.isFeatureEnabled('publishPartialSample')) {
publisher.publishPartialSample(samp);
} else {
publisher.publishSample(samp, subHelper.model);
}

u.logAPI(req, resultObj, dataValues);
return res.status(httpStatus.OK)
.json(u.responsify(samp, helper, req.method));
Expand Down Expand Up @@ -254,6 +259,7 @@ module.exports = {
*
* @param {IncomingMessage} req - The request object
* @param {ServerResponse} res - The response object
* @param {Function} next - The next middleware function in the stack
* @returns {Promise} - A promise that resolves to the response object,
* indicating merely that the bulk upsert request has been received.
*/
Expand Down Expand Up @@ -308,7 +314,11 @@ module.exports = {
.then((samples) => {
samples.forEach((sample) => {
if (!sample.isFailed) {
publisher.publishSample(sample, subHelper.model);
if (featureToggles.isFeatureEnabled('publishPartialSample')) {
publisher.publishPartialSample(sample);
} else {
publisher.publishSample(sample, subHelper.model);
}
}
});
});
Expand All @@ -323,8 +333,7 @@ module.exports = {
.catch((err) => // user does not have write permission for the sample
u.handleError(next, err, helper.modelName)
)
)
.catch(() => // user is not found. upsert anyway with no user
).catch(() => // user is not found. upsert anyway with no user
bulkUpsert(false)
.catch((err) => // the sample is write protected
u.handleError(next, err, helper.modelName)
Expand Down
31 changes: 30 additions & 1 deletion api/v1/controllers/subjects.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,36 @@ module.exports = {
*/
postSubject(req, res, next) {
validateRequest(req);
doPost(req, res, next, helper);
const { name, parentId, parentAbsolutePath } =
req.swagger.params.queryBody.value;

/*
* Fast fail: if cache is on AND parentId
* is not provided, check whether the subject exists in cache.
* Else if parentId is provided OR cache is off,
* do normal post.
*/
if (featureToggles.isFeatureEnabled(sampleStoreConstants.featureName) &&
featureToggles.isFeatureEnabled('getSubjectFromCache') &&
featureToggles.isFeatureEnabled('fastFailDuplicateSubject') &&
!u.looksLikeId(parentId)) {
const absolutePath = parentAbsolutePath ?
(parentAbsolutePath + '.' + name) : name;
redisSubjectModel.subjectInSampleStore(absolutePath)
.then((found) => {
if (found) {
throw new apiErrors.DuplicateResourceError(
'The subject lower case absolutePath must be unique');
}

doPost(req, res, next, helper);
})
.catch((err) => {
u.handleError(next, err, helper.modelName);
});
} else {
doPost(req, res, next, helper);
}
},

/**
Expand Down
16 changes: 16 additions & 0 deletions cache/models/subject.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ const filters = {
status: {},
};

/**
* Given absolutePath, return whether the subject is in cache
*
* @param {String} absolutePath
* @returns {Promise} resolves to true for found, false for not
*/
function subjectInSampleStore(absolutePath) {
const subjectKey = sampleStore.toKey('subject', absolutePath);

// get from cache
return redisClient.sismemberAsync(
sampleStore.constants.indexKey.subject, subjectKey);
}

/**
* Given a subject with its samples, aspect, aspectTags and sampleStatus filters
* are applied to the samples and the filtered samples are attached back to the
Expand Down Expand Up @@ -221,6 +235,8 @@ function convertStringsToNumbersAndAddParentAbsolutePath(subject) {
module.exports = {
completeSubjectHierarchy,

subjectInSampleStore,

/**
* Returns subject with filter options if provided.
* @param {Object} req - Request object
Expand Down
82 changes: 61 additions & 21 deletions cache/sampleStoreInit.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const samsto = require('./sampleStore');
const log = require('winston');
const samstoPersist = require('./sampleStorePersist');
const constants = samsto.constants;
const infoLoggingEnabled =
featureToggles.isFeatureEnabled('enableSampleStoreInfoLogging');

/**
* Deletes the previousStatuskey (that stores the previous value of the
Expand Down Expand Up @@ -88,8 +90,13 @@ function eradicate() {
}));
return deletePreviousStatus()
.then(() => Promise.all(promises))
.then(() => log.info('Sample Store eradicated from cache :D'))
.then(() => true);
.then(() => {
if (infoLoggingEnabled) {
log.info('Sample Store eradicated from cache :D');
}

return true;
});
} // eradicate

/**
Expand All @@ -106,8 +113,11 @@ function populateAspects() {
},
})
.then((allAspects) => {
const msg = `Starting to load ${allAspects.length} aspects to cache :|`;
log.info(msg);
if (infoLoggingEnabled) {
const msg = `Starting to load ${allAspects.length} aspects to cache :|`;
log.info(msg);
}

aspects = allAspects;
const getWritersPromises = [];

Expand Down Expand Up @@ -135,8 +145,13 @@ function populateAspects() {

cmds.push(['sadd', constants.indexKey.aspect, aspectIdx]);
return redisClient.batch(cmds).execAsync()
.then(() => log.info('Done loading aspects to cache :D'))
.then(() => true);
.then(() => {
if (infoLoggingEnabled) {
log.info('Done loading aspects to cache :D');
}

return true;
});
})
.catch(console.error); // eslint-disable-line no-console
} // populateAspects
Expand All @@ -147,10 +162,13 @@ function populateAspects() {
* @returns {Promise} which resolves to the list of redis batch responses.
*/
function populateSubjects() {
return Subject.findAll({ where: { isPublished: true } })
return Subject.findAll()
.then((subjects) => {
const msg = `Starting to load ${subjects.length} subjects to cache :|`;
log.info(msg);
if (infoLoggingEnabled) {
const msg = `Starting to load ${subjects.length} subjects to cache :|`;
log.info(msg);
}

const cmds = [];
subjects.forEach((s) => {
const key = samsto.toKey(constants.objectType.subject, s.absolutePath);
Expand All @@ -163,8 +181,13 @@ function populateSubjects() {
});

return redisClient.batch(cmds).execAsync()
.then(() => log.info('Done loading subjects to cache :D'))
.then(() => true);
.then(() => {
if (infoLoggingEnabled) {
log.info('Done loading subjects to cache :D');
}

return true;
});
})
.catch(console.error); // eslint-disable-line no-console
} // populateSubjects
Expand All @@ -177,8 +200,11 @@ function populateSubjects() {
function populateSamples() {
return Sample.findAll()
.then((samples) => {
const msg = `Starting to load ${samples.length} samples to cache :|`;
log.info(msg);
if (infoLoggingEnabled) {
const msg = `Starting to load ${samples.length} samples to cache :|`;
log.info(msg);
}

const sampleIdx = new Set();
const subjectSets = {};
const sampleHashes = {};
Expand Down Expand Up @@ -227,8 +253,13 @@ function populateSamples() {

// Return once all batches have completed.
return Promise.all(batchPromises)
.then(() => log.info('Done loading samples to cache :D'))
.then(() => true);
.then(() => {
if (infoLoggingEnabled) {
log.info('Done loading samples to cache :D');
}

return true;
});
})
.catch(console.error); // eslint-disable-line no-console
} // populateSamples
Expand All @@ -240,8 +271,11 @@ function populateSamples() {
* false if the feature is not enabled.
*/
function populate() {
const msg = 'Populating redis sample store from db started :|';
log.info(msg);
if (infoLoggingEnabled) {
const msg = 'Populating redis sample store from db started :|';
log.info(msg);
}

let resp;
const promises = [populateSubjects(), populateAspects()];
return Promise.all(promises)
Expand Down Expand Up @@ -286,14 +320,20 @@ function storeSampleToCacheOrDb() {
* "enableRedisSampleStore" flag has been changed from true to false
*/
if (currentStatus) {
log.info('"enableRedisSampleStore" flag was switched to true,' +
' so populating the cache from db');
if (infoLoggingEnabled) {
log.info('"enableRedisSampleStore" flag was switched to true, so ' +
'populating the cache from db');
}

return populate();
}

log.info('"enableRedisSampleStore" flag was switched to false' +
' so persisting to db from cache. The cache will be eradicated ' +
if (infoLoggingEnabled) {
log.info('"enableRedisSampleStore" flag was switched to false so ' +
'so persisting to db from cache. The cache will be eradicated ' +
'after the samples are persisted to db');
}

return samstoPersist.storeSampleToDb() // populate the sample table
.then(() => eradicate()); // eradicate the cache
}
Expand Down
31 changes: 24 additions & 7 deletions cache/sampleStorePersist.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const redisClient = require('./redisCache').client.sampleStore;
const samsto = require('./sampleStore');
const constants = samsto.constants;
const log = require('winston');
const infoLoggingEnabled =
featureToggles.isFeatureEnabled('enableSampleStoreInfoLogging');

/**
* Truncate the sample table in the DB and persist all the samples from redis
Expand All @@ -26,18 +28,27 @@ const log = require('winston');
* @returns {Promise} - which resolves to number of samples persisted to db
*/
function storeSampleToDb() {
log.info('Persist to db started :|. This will start by truncating the ' +
'sample table followed by persisting the sample to db');
if (infoLoggingEnabled) {
log.info('Persist to db started :|. This will start by truncating the ' +
'sample table followed by persisting the sample to db');
}

return Sample.destroy({ truncate: true, force: true })
.then(() => {
log.info('truncated the sample table :|');
if (infoLoggingEnabled) {
log.info('truncated the sample table :|');
}

return redisClient.smembersAsync(constants.indexKey.sample);
})
.then((keys) => keys.map((key) => ['hgetall', key]))
.then((cmds) => redisClient.batch(cmds).execAsync())
.then((res) => {
log.info('Preparing list of samples to persist...');
log.info(`Checking ${res.length} samples...`);
if (infoLoggingEnabled) {
log.info('Preparing list of samples to persist...');
log.info(`Checking ${res.length} samples...`);
}

const samplesToCreate = res.map((sample) => {
sample.relatedLinks = JSON.parse(sample.relatedLinks);
return sample;
Expand All @@ -51,11 +62,17 @@ function storeSampleToDb() {

return true;
});
log.info(`Bulk creating ${samplesToCreate.length} samples...`);
if (infoLoggingEnabled) {
log.info(`Bulk creating ${samplesToCreate.length} samples...`);
}

return Sample.bulkCreate(samplesToCreate);
})
.then((retval) => {
log.info('persisted redis sample store to db :D');
if (infoLoggingEnabled) {
log.info('persisted redis sample store to db :D');
}

return retval.length;
});
} // storeSampleToDb
Expand Down
Loading

0 comments on commit 22261d0

Please sign in to comment.