diff --git a/migrations/20180813045042-20150309023251-appportbindings-add-type.js b/migrations/20180813045042-20150309023251-appportbindings-add-type.js new file mode 100644 index 000000000..7db0360a1 --- /dev/null +++ b/migrations/20180813045042-20150309023251-appportbindings-add-type.js @@ -0,0 +1,18 @@ +'use strict'; + +var async = require('async'); + +exports.up = function(db, callback) { + async.series([ + db.runSql.bind(db, 'ALTER TABLE appPortBindings ADD COLUMN type VARCHAR(8) NOT NULL DEFAULT "tcp"'), + db.runSql.bind(db, 'ALTER TABLE appPortBindings DROP INDEX hostPort'), // this drops the unique constraint + db.runSql.bind(db, 'ALTER TABLE appPortBindings DROP PRIMARY KEY, ADD PRIMARY KEY(hostPort, type)') + ], callback); +}; + +exports.down = function(db, callback) { + async.series([ + db.runSql.bind(db, 'ALTER TABLE appPortBindings DROP PRIMARY KEY, ADD PRIMARY KEY(hostPort)'), + db.runSql.bind(db, 'ALTER TABLE appPortBindings DROP COLUMN type') + ], callback); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index b26ed00c6..94860f93f 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -92,6 +92,7 @@ CREATE TABLE IF NOT EXISTS apps( CREATE TABLE IF NOT EXISTS appPortBindings( hostPort INTEGER NOT NULL UNIQUE, + type VARCHAR(8) NOT NULL DEFAULT "tcp", environmentVariable VARCHAR(128) NOT NULL, appId VARCHAR(128) NOT NULL, FOREIGN KEY(appId) REFERENCES apps(id), diff --git a/src/appdb.js b/src/appdb.js index 98f59eb91..311b6a74e 100644 --- a/src/appdb.js +++ b/src/appdb.js @@ -71,7 +71,7 @@ var APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationSta 'apps.xFrameOptions', 'apps.sso', 'apps.debugModeJson', 'apps.robotsTxt', 'apps.enableBackup', 'apps.creationTime', 'apps.updateTime', 'apps.ownerId', 'apps.ts' ].join(','); -var PORT_BINDINGS_FIELDS = [ 'hostPort', 'environmentVariable', 'appId' ].join(','); +var PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId' ].join(','); function postProcess(result) { assert.strictEqual(typeof result, 'object'); @@ -96,14 +96,16 @@ function postProcess(result) { assert(result.environmentVariables === null || typeof result.environmentVariables === 'string'); result.portBindings = { }; - var hostPorts = result.hostPorts === null ? [ ] : result.hostPorts.split(','); - var environmentVariables = result.environmentVariables === null ? [ ] : result.environmentVariables.split(','); + let hostPorts = result.hostPorts === null ? [ ] : result.hostPorts.split(','); + let environmentVariables = result.environmentVariables === null ? [ ] : result.environmentVariables.split(','); + let portTypes = result.portTypes === null ? [ ] : result.portTypes.split(','); delete result.hostPorts; delete result.environmentVariables; + delete result.portTypes; for (var i = 0; i < environmentVariables.length; i++) { - result.portBindings[environmentVariables[i]] = { hostPort: parseInt(hostPorts[i], 10) }; + result.portBindings[environmentVariables[i]] = { hostPort: parseInt(hostPorts[i], 10), type: portTypes[i] }; } assert(result.accessRestrictionJson === null || typeof result.accessRestrictionJson === 'string'); @@ -133,10 +135,10 @@ function get(id, callback) { assert.strictEqual(typeof callback, 'function'); database.query('SELECT ' + APPS_FIELDS_PREFIXED + ',' - + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables' + + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables, GROUP_CONCAT(appPortBindings.type) AS portTypes' + ' FROM apps' + ' LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId' - + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND type = ?' + + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND subdomains.type = ?' + ' WHERE apps.id = ? GROUP BY apps.id', [ exports.SUBDOMAIN_TYPE_PRIMARY, id ], function (error, result) { if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); @@ -158,10 +160,10 @@ function getByHttpPort(httpPort, callback) { assert.strictEqual(typeof callback, 'function'); database.query('SELECT ' + APPS_FIELDS_PREFIXED + ',' - + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables' + + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables, GROUP_CONCAT(appPortBindings.type) AS portTypes' + ' FROM apps' + ' LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId' - + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND type = ?' + + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND subdomains.type = ?' + ' WHERE httpPort = ? GROUP BY apps.id', [ exports.SUBDOMAIN_TYPE_PRIMARY, httpPort ], function (error, result) { if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); @@ -182,10 +184,10 @@ function getByContainerId(containerId, callback) { assert.strictEqual(typeof callback, 'function'); database.query('SELECT ' + APPS_FIELDS_PREFIXED + ',' - + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables' + + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables, GROUP_CONCAT(appPortBindings.type) AS portTypes' + ' FROM apps' + ' LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId' - + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND type = ?' + + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND subdomains.type = ?' + ' WHERE containerId = ? GROUP BY apps.id', [ exports.SUBDOMAIN_TYPE_PRIMARY, containerId ], function (error, result) { if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); @@ -205,10 +207,10 @@ function getAll(callback) { assert.strictEqual(typeof callback, 'function'); database.query('SELECT ' + APPS_FIELDS_PREFIXED + ',' - + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables' + + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables, GROUP_CONCAT(appPortBindings.type) AS portTypes' + ' FROM apps' + ' LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId' - + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND type = ?' + + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND subdomains.type = ?' + ' GROUP BY apps.id ORDER BY apps.id', [ exports.SUBDOMAIN_TYPE_PRIMARY ], function (error, results) { if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); @@ -271,8 +273,8 @@ function add(id, appStoreId, manifest, location, domain, ownerId, portBindings, Object.keys(portBindings).forEach(function (env) { queries.push({ - query: 'INSERT INTO appPortBindings (environmentVariable, hostPort, appId) VALUES (?, ?, ?)', - args: [ env, portBindings[env].hostPort, id ] + query: 'INSERT INTO appPortBindings (environmentVariable, hostPort, type, appId) VALUES (?, ?, ?, ?)', + args: [ env, portBindings[env].hostPort, portBindings[env].type, id ] }); }); @@ -322,18 +324,19 @@ function getPortBindings(id, callback) { var portBindings = { }; for (var i = 0; i < results.length; i++) { - portBindings[results[i].environmentVariable] = results[i].hostPort; + portBindings[results[i].environmentVariable] = { hostPort: results[i].hostPort, type: results[i].type }; } callback(null, portBindings); }); } -function delPortBinding(hostPort, callback) { +function delPortBinding(hostPort, type, callback) { assert.strictEqual(typeof hostPort, 'number'); + assert.strictEqual(typeof type, 'string'); assert.strictEqual(typeof callback, 'function'); - database.query('DELETE FROM appPortBindings WHERE hostPort=?', [ hostPort ], function (error, result) { + database.query('DELETE FROM appPortBindings WHERE hostPort=? AND type=?', [ hostPort, type ], function (error, result) { if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); if (result.affectedRows !== 1) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); @@ -394,8 +397,8 @@ function updateWithConstraints(id, app, constraints, callback) { // replace entries by app id queries.push({ query: 'DELETE FROM appPortBindings WHERE appId = ?', args: [ id ] }); Object.keys(portBindings).forEach(function (env) { - var values = [ portBindings[env].hostPort, env, id ]; - queries.push({ query: 'INSERT INTO appPortBindings (hostPort, environmentVariable, appId) VALUES(?, ?, ?)', args: values }); + var values = [ portBindings[env].hostPort, portBindings[env].type, env, id ]; + queries.push({ query: 'INSERT INTO appPortBindings (hostPort, type, environmentVariable, appId) VALUES(?, ?, ?, ?)', args: values }); }); } diff --git a/src/apps.js b/src/apps.js index 3430522f4..926845d8c 100644 --- a/src/apps.js +++ b/src/apps.js @@ -45,6 +45,8 @@ exports = module.exports = { setOwner: setOwner, transferOwnership: transferOwnership, + PORT_TYPE_TCP: 'tcp', + // exported for testing _validateHostname: validateHostname, _validatePortBindings: validatePortBindings, @@ -216,7 +218,7 @@ function translatePortBindings(portBindings) { let result = {}; for (let portName in portBindings) { - result[portName] = { hostPort: portBindings[portName] }; + result[portName] = { hostPort: portBindings[portName], type: exports.PORT_TYPE_TCP }; } return result; } diff --git a/src/apptask.js b/src/apptask.js index 3e17a1ee7..9f142fc77 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -677,7 +677,7 @@ function update(app, callback) { async.each(Object.keys(currentPorts), function (portName, callback) { if (newPorts[portName]) return callback(); // port still in use - appdb.delPortBinding(currentPorts[portName], function (error) { + appdb.delPortBinding(currentPorts[portName], apps.PORT_TYPE_TCP, function (error) { if (error && error.reason === DatabaseError.NOT_FOUND) console.error('Portbinding does not exist in database.'); else if (error) return next(error); diff --git a/src/test/database-test.js b/src/test/database-test.js index 6362e9878..ee4893d01 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -221,7 +221,7 @@ describe('database', function () { manifest: { version: '0.1', dockerImage: 'docker/app0', healthCheckPath: '/', httpPort: 80, title: 'app0' }, httpPort: null, containerId: null, - portBindings: { port: { hostPort: 5678 } }, + portBindings: { port: { hostPort: 5678, type: 'tcp' } }, health: null, accessRestriction: null, lastBackupId: null, @@ -735,7 +735,7 @@ describe('database', function () { manifest: { version: '0.1', dockerImage: 'docker/app0', healthCheckPath: '/', httpPort: 80, title: 'app0' }, httpPort: null, containerId: null, - portBindings: { port: { hostPort: 5678 } }, + portBindings: { port: { hostPort: 5678, type: 'tcp' } }, health: null, accessRestriction: null, restoreConfig: null, @@ -821,7 +821,7 @@ describe('database', function () { appdb.getPortBindings(APP_0.id, function (error, bindings) { expect(error).to.be(null); expect(bindings).to.be.an(Object); - expect(bindings).to.be.eql({ port: '5678' }); + expect(bindings).to.be.eql({ port: { hostPort: '5678', type: 'tcp' } }); done(); }); });