diff --git a/configs/.env.development b/configs/.env.development index ed70fbf61..5afce8f17 100644 --- a/configs/.env.development +++ b/configs/.env.development @@ -25,3 +25,4 @@ REDIS_IPADDRESS=127.0.0.1 REDIS_PORT=6379 S3_CONTEXT_RESOURCE_BUCKET=runnable.context.resources.development ALLOW_ALL_CORS=true +AWS_ALIAS_HOST=us-west-2.compute.internal diff --git a/lib/models/mongo/instance.js b/lib/models/mongo/instance.js index e9646984c..0cd9a15b2 100644 --- a/lib/models/mongo/instance.js +++ b/lib/models/mongo/instance.js @@ -1158,37 +1158,92 @@ InstanceSchema.methods.getDependencies = function (params, cb) { cb = params params = {} } - var self = this return Instance.findByIdAsync(this._id) - .tap(function (instance) { + .tap(instance => { if (!instance) { - // the update failed - throw Boom.notFound('This instance could not be found!', { - instance: self._id.toString() + throw new Instance.NotFoundError('This instance could not be found!', { + instance: this._id.toString() }) } }) - .then(function (instance) { - return instance.dependencies || [] - }) - .filter(function removeSelf (dep) { - return self._id.toString() !== dep.id - }) - .filter(function filterByHostname (dep) { - if (!params.hostname) { - return true - } - return params.hostname === dep.elasticHostname + .then(instance => { + // this method will throw if there isn't a hostname, or an alias. So use that to control + // the flow of this + return instance.convertAliasToDependency(params.hostname) + .then(dependency => [dependency]) + .catch(Instance.NotFoundError, Instance.IncorrectStateError, () => { + // It wasn't an alias, so maybe it's a dep? + let dependencies = instance.dependencies || [] + if (params.hostname) { + // Only get the dependency that matches the hostname + dependencies = dependencies.filter(dep => params.hostname === dep.elasticHostname) + } else { + // Remove self from the list (if it exists) + dependencies = dependencies.filter(dep => this._id.toString() !== dep.id) + } + // Annotate dependencies with additional instance information (currently + // only adding network information for charon) + return Promise + .map(dependencies, dep => Instance.findByIdAsync(dep.instanceId)) + .filter(exists) + }) }) - .map(function fetchInstance (dep) { - // Annotate dependencies with additional instance information (currently - // only adding network information for charon) - return Instance.findByIdAsync(dep.instanceId) + .catch(Instance.NotFoundError, err => { + // the update failed + throw Boom.notFound('This instance could not be found!', { err }) }) - .filter(exists) .asCallback(cb) } +/** + * Given an alias (hostname), find the instance being referred to, and resolve it + * + * @param {String} alias - Hostname that some instance is using to reference another instance + * + * @resolves {Instance} - Dependent Instance model referenced by the given alias + * + * @throws Instance.IncorrectStateError - When we're looking for a dependency in an instance + * isn't a masterpod nor isolated + * @throws Instance.NotFoundError - When no alias is given + * @throws Instance.NotFoundError - When the alias isn't present in this Instance + * @throws Instance.NotFoundError - When the Dependency Instance fetch fails + * + */ +InstanceSchema.methods.convertAliasToDependency = Promise.method(function (alias) { + var log = logger.log.child({ + instanceId: keypather.get(this, '_id'), + aliases: keypather.get(this, 'aliases'), + alias, + method: 'InstanceSchema.methods.convertAliasesToDeps' + }) + log.info('called') + if (!alias) { + throw new Instance.NotFoundError({ alias }) + } + const base64Alias = new Buffer(alias).toString('base64') + const aliasModel = this.aliases[base64Alias] + if (!aliasModel) { + throw new Instance.NotFoundError({ alias, base64Alias, aliases: this.aliases }) + } + const query = { + 'contextVersion.context': aliasModel.contextId + } + if (this.masterPod) { + query.masterPod = true + } else if (this.isolated) { + query.isolated = this.isolated + } else { + // This shouldn't happen, so if it does, alert Nathan + throw new Instance.IncorrectStateError('be masterPod or isolated', 'neither') + } + return Instance.findOneAsync(query) + .tap(instance => { + if (!instance) { + throw new Instance.NotFoundError(query) + } + }) +}) + /** * Fetch all of the MasterPods that should be autoForked, given the list of instances which should * be autoDeployed. By using this list, we can find all of the child instances that were updated. diff --git a/lib/routes/instances/dependencies/index.js b/lib/routes/instances/dependencies/index.js index ab701d0b2..625fdf8c6 100644 --- a/lib/routes/instances/dependencies/index.js +++ b/lib/routes/instances/dependencies/index.js @@ -23,11 +23,16 @@ app.all('/instances/:id/dependencies*', }) }) +const AWS_ALIAS_HOST = new RegExp('\.' + process.env.AWS_ALIAS_HOST + '$', 'i') + app.get('/instances/:id/dependencies', mw.query('hostname').pick(), mw.query('hostname').require().then( mw.query('hostname').string(), - mw.query().set('hostname', 'query.hostname.toLowerCase()')), + function (req, res, next) { + req.query.hostname = req.query.hostname.replace(AWS_ALIAS_HOST, '').toLowerCase() + next() + }), function (req, res, next) { req.instance.getDependenciesAsync(req.query) .then(function (deps) { diff --git a/unit/models/mongo/instances.js b/unit/models/mongo/instances.js index 3b57539dd..17a42230f 100644 --- a/unit/models/mongo/instances.js +++ b/unit/models/mongo/instances.js @@ -2199,7 +2199,7 @@ describe('Instance Model Tests', function () { testContextVersionId, testContainerId, testContainerInfo - ).asCallback((err) => { + ).asCallback(err => { expect(err).to.be.an.instanceOf(Instance.NotFoundError) done() }) @@ -2212,7 +2212,7 @@ describe('Instance Model Tests', function () { testContextVersionId, testContainerId, testContainerInfo - ).asCallback((err) => { + ).asCallback(err => { if (err) { return done(err) } sinon.assert.calledOnce(Instance.findOneAndUpdateAsync) sinon.assert.calledWith(Instance.findOneAndUpdateAsync, { @@ -2234,4 +2234,104 @@ describe('Instance Model Tests', function () { }) }) }) // end markAsCreating + + describe('convertAliasToDependency', function () { + let instance + const key = 'hello' + const base64Key = new Buffer(key).toString('base64') + const contextId = 'asdasasdasd' + const isolatedId = '12312312=321' + + beforeEach(function (done) { + instance = mongoFactory.createNewInstance() + instance._doc.aliases[base64Key] = { + id: key, + contextId: contextId + } + instance._doc.masterPod = true + sinon.stub(Instance, 'findOneAsync').resolves() + done() + }) + + afterEach(function (done) { + Instance.findOneAsync.restore() + done() + }) + describe('errors', function () { + describe('NotFound', function () { + it('should throw when no alias given', (done) => { + instance.convertAliasToDependency() + .catch(err => { + expect(err).to.be.an.instanceOf(Instance.NotFoundError) + }) + .asCallback(done) + }) + it('should throw when alias not in instance', (done) => { + instance.convertAliasToDependency('asdasdas') + .catch(err => { + expect(err).to.be.an.instanceOf(Instance.NotFoundError) + expect(err.data.aliases).to.equal(instance._doc.aliases) + }) + .asCallback(done) + }) + it('should throw when instance not masterPod nor isolated', (done) => { + delete instance._doc.masterPod + delete instance._doc.isolated + instance.convertAliasToDependency(key) + .catch(err => { + expect(err).to.be.an.instanceOf(Instance.IncorrectStateError) + }) + .asCallback(done) + }) + it('should throw when instance not returned from Mongo', (done) => { + instance.convertAliasToDependency(key) + .catch(err => { + expect(err).to.be.an.instanceOf(Instance.NotFoundError) + }) + .asCallback(done) + }) + }) + }) + describe('Success', function () { + let depInstance + beforeEach(function (done) { + depInstance = mongoFactory.createNewInstance('dep') + Instance.findOneAsync.resolves(depInstance) + done() + }) + describe('masterPod', function () { + it('should return instance when alias matches', (done) => { + instance.convertAliasToDependency(key) + .then(dep => { + expect(dep._id).to.equal(depInstance._id) + sinon.assert.calledOnce(Instance.findOneAsync) + sinon.assert.calledWith(Instance.findOneAsync, { + 'contextVersion.context': contextId, + masterPod: true + }) + }) + .asCallback(done) + }) + }) + describe('isolated', function () { + beforeEach(function (done) { + delete instance._doc.masterPod + instance._doc.isolated = isolatedId + done() + }) + it('should return instance when alias matches', (done) => { + instance.convertAliasToDependency(key) + .then(dep => { + expect(dep._id).to.equal(depInstance._id) + sinon.assert.calledOnce(Instance.findOneAsync) + sinon.assert.calledWith(Instance.findOneAsync, { + 'contextVersion.context': contextId, + isolated: isolatedId + }) + }) + .asCallback(done) + }) + }) + }) + }) })