diff --git a/lib/models/api.js b/lib/models/api.js index ff50b85..155ce6b 100644 --- a/lib/models/api.js +++ b/lib/models/api.js @@ -113,12 +113,18 @@ api._shouldBypassAuth = function (req) { /** * Proxy request to detention if container is not running. * Proxy request to container is running + * @param {Object} targetNaviEntryInstance + * @param {String} shortHash + * @param {String} reqUrl + * @param {Object} req + * @param {Function} next */ -api._processTargetInstance = function (targetNaviEntryInstance, reqUrl, req, next) { +api._processTargetInstance = function (targetNaviEntryInstance, shortHash, reqUrl, req, next) { var logData = { tx: true, targetNaviEntryInstance: targetNaviEntryInstance, - reqUrl: reqUrl + reqUrl: reqUrl, + shortHash: shortHash }; log.info(logData, 'api._processTargetInstance'); @@ -127,14 +133,17 @@ api._processTargetInstance = function (targetNaviEntryInstance, reqUrl, req, nex return next(ErrorCat.create(404, 'Not Found')); } + // Adding these to req object to make values available in proxy module's error event handler. + req.shortHash = shortHash; + req.elasticUrl = reqUrl; + // for error-page module if container is unresponsive req.targetBranch = targetNaviEntryInstance.branch; - if (!targetNaviEntryInstance.running) { log.trace(logData, '_processTargetInstance !running'); req.targetHost = errorPage.generateErrorUrl('not_running', { elasticUrl: reqUrl, - targetBranch: targetNaviEntryInstance.branch + shortHash: shortHash }); return next(); } @@ -194,29 +203,35 @@ api._getTargetHostElastic = function (logData, req, next) { log.trace(logData, '_getTargetHostElastic redis.lrange mongo.fetchNaviEntry isBrowser !refererUrl'); // document might not have a userMappings key yet - var mappedInstanceId = (naviEntry.userMappings) ? + var mappedInstanceShortHash = (naviEntry.userMappings) ? naviEntry.userMappings[req.session.userId] : null; - if (!mappedInstanceId) { + if (!mappedInstanceShortHash) { log.trace(logData, '_getTargetHostElastic redis.lrange mongo.fetchNaviEntry isBrowser !refererUrl '+ - '!mappedInstanceId'); + '!mappedInstanceShortHash'); // use master - targetNaviEntryInstance = mongo.constructor.findMasterPodBranch(naviEntry); + var findResult = mongo.constructor.findMasterPodBranch(naviEntry); + targetNaviEntryInstance = findResult.directUrlObj; + mappedInstanceShortHash = findResult.directUrlShortHash; } else { log.trace(put({ - mappedInstanceId: mappedInstanceId + mappedInstanceShortHash: mappedInstanceShortHash }, logData), '_getTargetHostElastic redis.lrange mongo.fetchNaviEntry isBrowser !refererUrl '+ - 'mappedInstanceId'); - targetNaviEntryInstance = naviEntry.directUrls[mappedInstanceId]; + 'mappedInstanceShortHash'); + targetNaviEntryInstance = naviEntry.directUrls[mappedInstanceShortHash]; } - return api._processTargetInstance(targetNaviEntryInstance, reqUrl, req, next); + return api._processTargetInstance(targetNaviEntryInstance, mappedInstanceShortHash, + reqUrl, req, next); } } else { // if not browser, proxy to master - log.trace(logData, '_getTargetHostElastic redis.lrange mongo.fetchNaviEntry !isBrowser'); - targetNaviEntryInstance = mongo.constructor.findMasterPodBranch(naviEntry); - return api._processTargetInstance(targetNaviEntryInstance, reqUrl, req, next); + log.trace(logData, 'getTargetHost redis.lrange mongo.fetchNaviEntry !isBrowser'); + var findResult = mongo.constructor.findMasterPodBranch(naviEntry); + targetNaviEntryInstance = findResult.directUrlObj; + return api._processTargetInstance(targetNaviEntryInstance, + findResult.directUrlShortHash, + reqUrl, req, next); } }); }; @@ -251,9 +266,9 @@ api._getTargetHostElasticReferer = function (logData, naviEntry, req, next) { var refererUserMappedInstanceId = (naviEntry.refererNaviEntry.userMappings) ? naviEntry.refererNaviEntry.userMappings[req.session.userId] : null; var refererNaviEntryInstance; + if (refererUserMappedInstanceId) { - refererNaviEntryInstance = - naviEntry.refererNaviEntry.directUrls[refererUserMappedInstanceId]; + refererNaviEntryInstance = naviEntry.refererNaviEntry.directUrls[refererUserMappedInstanceId]; log.trace(put({ refererUserMappedInstanceId: refererUserMappedInstanceId, refererNaviEntryInstance: refererNaviEntryInstance @@ -262,8 +277,9 @@ api._getTargetHostElasticReferer = function (logData, naviEntry, req, next) { 'refererNaviEntryInstance'); } else { // no user-mapping for current user and referer instance, use master - refererNaviEntryInstance = - mongo.constructor.findMasterPodBranch(naviEntry.refererNaviEntry); + var findResult = mongo.constructor.findMasterPodBranch(naviEntry.refererNaviEntry); + refererNaviEntryInstance = keypather.get(findResult, 'directUrlObj'); + refererUserMappedInstanceId = keypather.get(findResult, 'directUrlShortHash'); log.trace(put({ refererUserMappedInstanceId: refererUserMappedInstanceId, refererNaviEntryInstance: refererNaviEntryInstance @@ -293,7 +309,9 @@ api._getTargetHostElasticReferer = function (logData, naviEntry, req, next) { * Referer url is a valid runnable elasticUrl however there might be no defined DNS mappings. * Proxy to the masterPod instance if so. */ - targetNaviEntryInstance = mongo.constructor.findMasterPodBranch(naviEntry); + //targetNaviEntryInstance = mongo.constructor.findMasterPodBranch(naviEntry); + var findResult = mongo.constructor.findMasterPodBranch(naviEntry.refererNaviEntry); + targetNaviEntryInstance = keypather.get(findResult, 'directUrlObj'); } if (!targetNaviEntryInstance) { @@ -304,9 +322,15 @@ api._getTargetHostElasticReferer = function (logData, naviEntry, req, next) { }, logData), '_getTargetHostElasticReferer redis.lrange mongo.fetchNaviEntry isBrowser !targetNaviEntryInstance '+ 'use master'); - targetNaviEntryInstance = mongo.constructor.findMasterPodBranch(naviEntry); + + var findResult = mongo.constructor.findMasterPodBranch(naviEntry); + targetNaviEntryInstance = findResult.directUrlObj; + return api._processTargetInstance(targetNaviEntryInstance, + findResult.directUrlShortHash, + reqUrl, req, next); + } - return api._processTargetInstance(targetNaviEntryInstance, reqUrl, req, next); + return api._processTargetInstance(targetNaviEntryInstance, instanceShortHash, reqUrl, req, next); }; /** diff --git a/lib/models/error-page.js b/lib/models/error-page.js index 2d647d3..3d73fbd 100644 --- a/lib/models/error-page.js +++ b/lib/models/error-page.js @@ -40,7 +40,7 @@ function generateErrorUrl(type, opts) { log.trace(logData, 'generateErrorUrl signin'); } else if (type === 'not_running' || type === 'unresponsive' || type === 'ports') { query.elasticUrl = opts.elasticUrl; - query.targetBranch = opts.targetBranch; + query.shortHash = opts.shortHash; } else { throw ErrorCat.createAndReport(500, 'invalid error page'); } diff --git a/lib/models/mongo.js b/lib/models/mongo.js index 85fa7c6..f71d405 100644 --- a/lib/models/mongo.js +++ b/lib/models/mongo.js @@ -122,7 +122,8 @@ Mongo.prototype.setUserMapping = function (elasticUrl, userId, instanceShortHash }; /** - * Find nested directUrl object on a naviEntry document that is a masterPod branch + * Find nested directUrl object and its key (shortHash on a naviEntry document that is a masterPod + * branch * @param {String} branchName * @return {Object|undefined} */ @@ -136,7 +137,10 @@ Mongo.findMasterPodBranch = function (naviEntry) { for (var i = 0, len = shortHashes.length; i < len; i++) { var directUrlObj = naviEntry.directUrls[shortHashes[i]]; if (directUrlObj.masterPod) { - return directUrlObj; + return { + directUrlShortHash: shortHashes[i], + directUrlObj: directUrlObj + }; } } log.warn(logData, 'findMasterPodBranch !match'); diff --git a/lib/models/proxy.js b/lib/models/proxy.js index 74621f9..1aa77f0 100644 --- a/lib/models/proxy.js +++ b/lib/models/proxy.js @@ -35,7 +35,6 @@ function Proxy () { log.info('Proxy constructor'); var self = this; this.proxy = httpProxy.createProxyServer({}); - // setup error handling this.proxy.on('error', function (err, req/*, proxiedRes */) { var res = req.res; var logData = { @@ -44,11 +43,16 @@ function Proxy () { req: req }; log.error(logData, 'proxy.on error'); + /** + * we need to keep the didError logic. sometimes (not sure why) but we try to proxy the same + * request twice (in some network cases) and navi will crash saying you can't send on a request + * that is already ended + */ if (!req.didError) { req.didError = true; var errorUrl = errorPage.generateErrorUrl('unresponsive', { - elasticUrl: '', - targetBranch: '' + shortHash: req.shortHash, + elasticUrl: req.elasticUrl }); var targetHost = getHostAndAugmentReq(req, errorUrl); log.trace(put({ @@ -165,12 +169,11 @@ Proxy.prototype._streamRes = function (targetRes, proxiedRes, res) { log.trace(logData, '_streamRes resIsHtml'); delete targetRes.headers['content-length']; var scriptInjectStream = scriptInjectResStreamFactory.create(xhrPatchScript, resIsGziped); - proxiedRes - .pipe(scriptInjectStream.input); - scriptInjectStream.output - .pipe(res); - } - else { + proxiedRes.pipe(scriptInjectStream.input); + + // Added these logs to debug timing of script injection + scriptInjectStream.output.pipe(res); + } else { log.trace(logData, '_streamRes !resIsHtml'); // if the response type is not html transformRes should not modify the response // finally pipe target response data to the real response diff --git a/test/unit/api.js b/test/unit/api.js index 247d1c2..4c6d08c 100644 --- a/test/unit/api.js +++ b/test/unit/api.js @@ -13,6 +13,7 @@ var lab = exports.lab = Lab.script(); //var errorPage = require('models/error-page.js'); var api = require('models/api.js'); +var log = require('middlewares/logger')(__filename).log; var mongo = require('models/mongo'); var naviEntriesFixtures = require('../fixture/navi-entries'); var naviRedisEntriesFixture = require('../fixture/navi-redis-entries'); @@ -148,6 +149,7 @@ describe('api.js unit test', function () { expect(test).to.equal(result); done(); }); + it('should add https', function (done) { var test = api._getUrlFromRequest({ isBrowser: true, @@ -219,23 +221,47 @@ describe('api.js unit test', function () { }); describe('_processTargetInstance', function () { + beforeEach(function (done) { + sinon.stub(api, '_getDestinationProxyUrl'); + done(); + }); + + afterEach(function (done) { + api._getDestinationProxyUrl.restore(); + done(); + }); + it('should next with error if !targetNaviEntryInstance', function (done) { - api._processTargetInstance(null, '', {}, function (err) { + api._processTargetInstance(null, '', '', {}, function (err) { expect(err.message).to.equal('Not Found'); done(); }); }); - it('should set req.targetHost if !running', function (done) { + it('should set req.targetHost to error url if !running', function (done) { var req = {}; var reqUrl = 'api-staging-codenow.runnableapp.com'; api._processTargetInstance({ running: false, branch: 'master' - }, reqUrl, req, function (err) { + }, '55555', reqUrl, req, function (err) { expect(err).to.be.undefined(); expect(req.targetHost).to.equal('http://localhost:55551?type=not_running&elasticUrl='+ - reqUrl+'&targetBranch=master'); + reqUrl+'&shortHash=55555'); + done(); + }); + }); + + it('should set req.targetHost to container host & port if running', function (done) { + api._getDestinationProxyUrl.returns('http://0.0.0.0:600'); + var req = {}; + var reqUrl = 'api-staging-codenow.runnableapp.com'; + api._processTargetInstance({ + running: true, + branch: 'master' + }, '55555', reqUrl, req, function (err) { + expect(err).to.be.undefined(); + expect(req.targetHost).to.equal('http://0.0.0.0:600'); done(); }); }); @@ -438,35 +464,45 @@ describe('api.js unit test', function () { it('should handle navientires document with no user-mappings', function (done) { api._processTargetInstance.restore(); mongo.constructor.findAssociationShortHashByElasticUrl.restore(); + var restore = put({}, naviEntriesFixtures.refererNaviEntry); delete naviEntriesFixtures.refererNaviEntry.userMappings; + api.getTargetHost(req, {}, function (err) { expect(err).to.be.undefined(); expect(req.targetHost).to.equal('http://0.0.0.2:39942'); - naviEntriesFixtures.refererNaviEntry.userMappings = restore; + naviEntriesFixtures.refererNaviEntry = restore; done(); }); }); it('should next with error if navientries document with no user-mappings and no '+ 'masterpod', function (done) { + api._processTargetInstance.restore(); mongo.constructor.findAssociationShortHashByElasticUrl.restore(); + var restore = put({}, naviEntriesFixtures.refererNaviEntry); delete naviEntriesFixtures.refererNaviEntry.userMappings; naviEntriesFixtures.refererNaviEntry.directUrls.aaaaa1.masterPod = false; + api.getTargetHost(req, {}, function (err) { expect(err.message).to.equal('Not Found'); naviEntriesFixtures.refererNaviEntry.userMappings = restore; naviEntriesFixtures.refererNaviEntry.directUrls.aaaaa1.masterPod = true; + + naviEntriesFixtures.refererNaviEntry = restore; + done(); }); }); it('should default to masterPod if !instanceShortHash', function (done) { var mockNaviEntry = {}; - sinon.stub(mongo.constructor, 'findMasterPodBranch', function () { - return mockNaviEntry; + + sinon.stub(mongo.constructor, 'findMasterPodBranch').returns({ + directUrlObj: mockNaviEntry, + directUrlShortHash: 'FFFF' }); api.getTargetHost(req, {}, function (err) { @@ -484,8 +520,10 @@ describe('api.js unit test', function () { sinon.stub(mongo.constructor, 'findMasterPodBranch'); mongo.constructor.findMasterPodBranch.onFirstCall().returns({}); mongo.constructor.findMasterPodBranch.onSecondCall().returns(undefined); - mongo.constructor.findMasterPodBranch.onThirdCall().returns({ - masterPod: true + mongo.constructor.findMasterPodBranch.onSecondCall().returns({ + directUrlObj: { + masterPod: true + } }); api.getTargetHost(req, {}, function () { diff --git a/test/unit/error-page.js b/test/unit/error-page.js index 9b4d5a6..89d8dfe 100644 --- a/test/unit/error-page.js +++ b/test/unit/error-page.js @@ -29,39 +29,39 @@ describe('error-page.js unit test', function () { it('should generate error url for not-running error', function (done) { var proxyUrl = errorPage.generateErrorUrl('not_running', { elasticUrl: 'api-staging-codenow.runnableapp.com', - targetBranch: 'master' + shortHash: '555' }); expect(proxyUrl).to .equal( 'http://localhost:55551?'+ 'type=not_running&elasticUrl=api-staging-codenow.runnableapp.com'+ - '&targetBranch=master'); + '&shortHash=555'); done(); }); it('should generate error url for unresponsive error', function (done) { var proxyUrl = errorPage.generateErrorUrl('unresponsive', { elasticUrl: 'api-staging-codenow.runnableapp.com', - targetBranch: 'master' + shortHash: '555' }); expect(proxyUrl).to .equal( 'http://localhost:55551?'+ 'type=unresponsive&elasticUrl=api-staging-codenow.runnableapp.com'+ - '&targetBranch=master'); + '&shortHash=555'); done(); }); it('should generate error url for ports error', function (done) { var proxyUrl = errorPage.generateErrorUrl('ports', { elasticUrl: 'api-staging-codenow.runnableapp.com', - targetBranch: 'master' + shortHash: '555' }); expect(proxyUrl).to .equal( 'http://localhost:55551?'+ 'type=ports&elasticUrl=api-staging-codenow.runnableapp.com'+ - '&targetBranch=master'); + '&shortHash=555'); done(); }); @@ -69,7 +69,7 @@ describe('error-page.js unit test', function () { function throws () { errorPage.generateErrorUrl('skjfasghasdg', { elasticUrl: 'api-staging-codenow.runnableapp.com', - targetBranch: 'master' + shortHash: '555' }); } expect(throws).to.throw(); diff --git a/test/unit/mongo.js b/test/unit/mongo.js index f9cf092..7e05204 100644 --- a/test/unit/mongo.js +++ b/test/unit/mongo.js @@ -232,7 +232,8 @@ describe('lib/models/mongodb', function () { var copy = put({}, naviEntryFixtures); copy.directUrls.e4rov2.masterPod = false; copy.directUrls.e4v7ve.masterPod = true; - var masterPod = mongo.constructor.findMasterPodBranch(copy); + var findResult = mongo.constructor.findMasterPodBranch(copy); + var masterPod = findResult.directUrlObj; expect(masterPod.masterPod).to.equal(true); expect(masterPod).to.equal(copy.directUrls.e4v7ve); done();