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
Showing
10 changed files
with
392 additions
and
2 deletions.
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
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,29 @@ | ||
const _ = require('lodash'); | ||
const $Docker = require('dockerode'); | ||
|
||
function Docker() { | ||
this.dockerAPI = new $Docker({ socketPath: '/var/run/docker.sock' }); | ||
} | ||
|
||
/** | ||
* @param {String} name | ||
* @param {String} image | ||
* @param {Object} options | ||
* @returns Promise<Object> | ||
*/ | ||
Docker.prototype.createContainer = function (name, image, options) { | ||
return this.dockerAPI.createContainer({ | ||
name: name, | ||
Image: image, | ||
AttachStdin: _.get(options, 'AttachStdin', false), | ||
AttachStdout: _.get(options, 'AttachStdout', true), | ||
AttachStderr: _.get(options, 'AttachStderr', true), | ||
Tty: _.get(options, 'Tty', true), | ||
OpenStdin: _.get(options, 'OpenStdin', false), | ||
StdinOnce: _.get(options, 'StdinOnce', false), | ||
Env: _.get(options, 'Env'), | ||
PortBindings: _.get(options, 'PortBindings') | ||
}); | ||
} | ||
|
||
module.exports = Docker; |
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,58 @@ | ||
'use strict'; | ||
|
||
const Promise = require('bluebird'); | ||
const _ = require('lodash'); | ||
|
||
function DockerContainer(docker, name, image, options) { | ||
this.container = docker.createContainer(name, image, options); | ||
} | ||
|
||
/** | ||
* @returns {Promise} | ||
*/ | ||
DockerContainer.prototype.start = function () { | ||
return this.container.then((c) => | ||
c.start().then(() => { | ||
console.log(`#~ Started container ${c.id}`); | ||
return this.waitReady(); | ||
}) | ||
) | ||
}; | ||
|
||
/** | ||
* @returns {Promise} | ||
*/ | ||
DockerContainer.prototype.waitReady = function () { | ||
return Promise.resolve(this); | ||
} | ||
|
||
/** | ||
* @returns {Promise} | ||
*/ | ||
DockerContainer.prototype.stop = function () { | ||
return this.container.then((c) => | ||
c.stop().then(() => | ||
console.log(`#~ Stopped container ${c.id}`) | ||
) | ||
.catch((err) => { | ||
if (err.statusCode !== 304) { | ||
throw err; | ||
} | ||
}) | ||
); | ||
} | ||
|
||
/** | ||
* @returns {Promise} | ||
*/ | ||
DockerContainer.prototype.destroy = function () { | ||
return this.stop().then(() => | ||
this.container.then((c) => | ||
c.remove().then(() => | ||
console.log(`#~ Removed container ${c.id}`) | ||
) | ||
) | ||
); | ||
} | ||
|
||
module.exports = DockerContainer; |
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,21 @@ | ||
'use strict'; | ||
|
||
var os = require('os'); | ||
var proc = require('child_process') | ||
var config = require('../knexfile'); | ||
var knex = require('../../knex'); | ||
var Promise = require('bluebird'); | ||
|
||
if (canRunDockerTests()) { | ||
Promise.each(Object.keys(config), function(dialectName) { | ||
if (config[dialectName].docker) { | ||
return require('./reconnect')(config[dialectName], knex); | ||
} | ||
}); | ||
} | ||
|
||
function canRunDockerTests() { | ||
var isLinux = os.platform() === 'linux'; | ||
var hasDocker = proc.execSync('docker -v 1>/dev/null 2>&1 ; echo $?').toString('utf-8') === '0\n'; | ||
return isLinux && hasDocker; | ||
} |
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,52 @@ | ||
'use strict'; | ||
|
||
const Promise = require('bluebird'); | ||
const _ = require('lodash'); | ||
const DockerContainer = require('../dockerContainer'); | ||
|
||
function MySQLContainer(docker, options) { | ||
var name = _.get(options, 'container'); | ||
var image = _.get(options, 'image'); | ||
var username = _.get(options, 'username'); | ||
var password = _.get(options, 'password'); | ||
var hostPort = _.get(options, 'hostPort'); | ||
DockerContainer.call(this, docker, name, image, { | ||
Env: [ `MYSQL_ROOT_PASSWORD=root` ], | ||
PortBindings: { | ||
'3306/tcp': [{ | ||
HostPort: `${hostPort}` | ||
}] | ||
} | ||
}); | ||
} | ||
|
||
MySQLContainer.prototype = Object.create(DockerContainer.prototype); | ||
|
||
/** | ||
* @returns {Promise} | ||
*/ | ||
MySQLContainer.prototype.waitReady = function () { | ||
return this.container.then((c) => { | ||
return new Promise((resolve) => { | ||
c.exec({ | ||
AttachStdout: true, | ||
Cmd: [ | ||
'sh', | ||
'-c', | ||
'until mysqladmin ping -h 127.0.0.1 --silent; do echo "Waiting for mysql readiness" && sleep 2; done' | ||
] | ||
}) | ||
.then((exec) => | ||
exec.start({ Detach: false, Tty: true }) | ||
) | ||
.then(({ output }) => { | ||
output.on('data', (data) => { | ||
console.log(data.toString('utf-8').trim()); | ||
}); | ||
output.on('end', () => resolve(this)); | ||
}); | ||
}) | ||
}); | ||
} | ||
|
||
module.exports = MySQLContainer; |
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,52 @@ | ||
'use strict'; | ||
|
||
const Promise = require('bluebird'); | ||
const _ = require('lodash'); | ||
const DockerContainer = require('../dockerContainer'); | ||
|
||
function PostgresContainer(docker, options) { | ||
var name = _.get(options, 'container'); | ||
var image = _.get(options, 'image'); | ||
var username = _.get(options, 'username'); | ||
var password = _.get(options, 'password'); | ||
var hostPort = _.get(options, 'hostPort'); | ||
DockerContainer.call(this, docker, name, image, { | ||
Env: [`POSTGRES_USER=${username}`, `POSTGRES_PASSWORD=${password}`], | ||
PortBindings: { | ||
'5432/tcp': [{ | ||
HostPort: `${hostPort}` | ||
}] | ||
} | ||
}); | ||
} | ||
|
||
PostgresContainer.prototype = Object.create(DockerContainer.prototype); | ||
|
||
/** | ||
* @returns {Promise} | ||
*/ | ||
PostgresContainer.prototype.waitReady = function () { | ||
return this.container.then((c) => { | ||
return new Promise((resolve) => { | ||
c.exec({ | ||
AttachStdout: true, | ||
Cmd: [ | ||
'sh', | ||
'-c', | ||
'until pg_isready; do sleep 1; done' | ||
] | ||
}) | ||
.then((exec) => | ||
exec.start({ Detach: false, Tty: true }) | ||
) | ||
.then(({ output }) => { | ||
output.on('data', function (data) { | ||
console.log(data.toString('utf-8').trim()); | ||
}); | ||
output.on('end', () => resolve(this)); | ||
}); | ||
}) | ||
}); | ||
} | ||
|
||
module.exports = PostgresContainer; |
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,147 @@ | ||
/*global afterEach, before, expect, describe, it, testPromise*/ | ||
'use strict'; | ||
|
||
var Docker = require('./docker'); | ||
var Promise = testPromise; | ||
|
||
module.exports = function(config, knex) { | ||
|
||
var dockerConf = config.docker; | ||
var ContainerClass = require(dockerConf.factory); | ||
|
||
/** | ||
* Make sure the connections in the connection pool are not | ||
* evicted on timeout, they should only be evicted on error. | ||
*/ | ||
var EVICTION_RUN_INTERVAL_MILLIS = dockerConf.timeout; | ||
var IDLE_TIMEOUT_MILLIS = dockerConf.timeout; | ||
var ACQUIRE_CONNECTION_TIMEOUT = 10 * 1000; | ||
var ACQUIRE_TIMEOUT_MILLIS = 10 * 1000; | ||
|
||
var docker; | ||
var connectionPool; | ||
var container; | ||
|
||
|
||
describe('using database as a docker container', function () { | ||
|
||
this.timeout(dockerConf.timeout); | ||
|
||
before(function () { | ||
docker = new Docker(); | ||
}); | ||
|
||
afterEach(function () { | ||
return sequencedPromise( | ||
() => console.log('>> Destroying container'), | ||
() => container.destroy(), | ||
() => console.log('>> Destroying pool'), | ||
() => connectionPool.destroy(), | ||
() => console.log('>> Destroyed all') | ||
); | ||
}); | ||
|
||
describe('start container and wait until it is ready', function () { | ||
|
||
beforeEach(function () { | ||
container = new ContainerClass(docker, dockerConf); | ||
return container.start().then(() => waitReadyForQueries()); | ||
}); | ||
|
||
describe('initialize connection pool', function () { | ||
beforeEach(function () { | ||
connectionPool = createPool(); | ||
}); | ||
|
||
it('connection pool can query', function () { | ||
return testQuery(connectionPool); | ||
}); | ||
|
||
describe('stop db-container and expect queries to fail', function () { | ||
|
||
beforeEach(function () { | ||
return container.stop(); | ||
}); | ||
|
||
it('connection pool can not query x10', function () { | ||
var promises = []; | ||
for (var i = 0; i < 10; i += 1) { | ||
promises.push( | ||
testQuery(connectionPool) | ||
.then(() => { throw new Error('Failure expected'); }) | ||
.catch((err) => expect(err.message).to.not.equal('Failure expected')) | ||
); | ||
} | ||
return Promise.all(promises); | ||
}); | ||
|
||
describe('restart db-container and keep using connection pool', function () { | ||
beforeEach(function () { | ||
return container.start().then(() => waitReadyForQueries()); | ||
}); | ||
|
||
it('connection pool can query x10', function () { | ||
var promises = []; | ||
for (var i = 0; i < 10; i += 1) { | ||
promises.push(testQuery(connectionPool)); | ||
} | ||
return Promise.all(promises); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}) | ||
}); | ||
|
||
function testQuery(pool) { | ||
return pool.raw(`SELECT 10 as ten`).then((result) => { | ||
expect(result.rows || result[0]).to.deep.equal([{ ten: 10 }]); | ||
}); | ||
} | ||
|
||
function sequencedPromise(...blocks) { | ||
const base = Promise.resolve(true); | ||
const order = (prev, block) => prev.then(() => block()); | ||
return blocks.reduce(order, base); | ||
} | ||
|
||
function createPool() { | ||
return knex({ | ||
// debug: true, | ||
client: dockerConf.client, | ||
acquireConnectionTimeout: ACQUIRE_CONNECTION_TIMEOUT, | ||
pool: { | ||
min: 7, | ||
max: 7, | ||
idleTimeoutMillis: IDLE_TIMEOUT_MILLIS, | ||
acquireTimeoutMillis: ACQUIRE_TIMEOUT_MILLIS, | ||
evictionRunIntervalMillis: EVICTION_RUN_INTERVAL_MILLIS | ||
}, | ||
connection: { | ||
database: dockerConf.database, | ||
port: dockerConf.hostPort, | ||
user: dockerConf.username, | ||
password: dockerConf.password, | ||
host: '127.0.0.1' | ||
} | ||
}); | ||
} | ||
|
||
function waitReadyForQueries(attempt = 0) { | ||
return new Promise(function (resolve, reject) { | ||
console.log(`#~ Waiting to be ready for queries #${attempt}`); | ||
var pool = createPool(); | ||
pool.raw('SELECT 1 as one') | ||
.then(() => pool.destroy().then(resolve)) | ||
.catch((a) => { | ||
pool.destroy().then(() => { | ||
if (attempt < 20) { | ||
setTimeout(() => resolve(waitReadyForQueries(attempt + 1)), 1000); | ||
} else { | ||
reject(attempt); | ||
} | ||
}) | ||
}); | ||
}); | ||
} | ||
}; |
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
Oops, something went wrong.