Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker test fixtures #275

Merged
merged 2 commits into from
Apr 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ env:
- PYTHONASYNCIODEBUG=1
- PYTHONASYNCIODEBUG=

services:
- docker

matrix:
include:
- python: 3.6
Expand Down
2 changes: 2 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ipython==6.2.1
pytest==3.4.2
pytest-cov==2.5.1
pytest-sugar==0.9.1
PyMySQL>=0.7.5
docker==3.1.4
sphinx==1.7.1
sphinxcontrib-asyncio==0.2.0
sqlalchemy==1.2.5
17 changes: 12 additions & 5 deletions tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _connect_all(self):
def setUp(self):
super(AIOPyMySQLTestCase, self).setUp()
self.host = os.environ.get('MYSQL_HOST', 'localhost')
self.port = os.environ.get('MYSQL_PORT', 3306)
self.port = int(os.environ.get('MYSQL_PORT', 3306))
self.user = os.environ.get('MYSQL_USER', 'root')
self.db = os.environ.get('MYSQL_DB', 'test_pymysql')
self.other_db = os.environ.get('OTHER_MYSQL_DB', 'test_pymysql2')
Expand All @@ -47,7 +47,7 @@ def tearDown(self):

@asyncio.coroutine
def connect(self, host=None, user=None, password=None,
db=None, use_unicode=True, no_delay=None, **kwargs):
db=None, use_unicode=True, no_delay=None, port=None, **kwargs):
if host is None:
host = self.host
if user is None:
Expand All @@ -56,16 +56,20 @@ def connect(self, host=None, user=None, password=None,
password = self.password
if db is None:
db = self.db
if port is None:
port = self.port
conn = yield from aiomysql.connect(loop=self.loop, host=host,
user=user, password=password,
db=db, use_unicode=use_unicode,
no_delay=no_delay, **kwargs)
no_delay=no_delay, port=port,
**kwargs)
self.addCleanup(conn.close)
return conn

@asyncio.coroutine
def create_pool(self, host=None, user=None, password=None,
db=None, use_unicode=True, no_delay=None, **kwargs):
db=None, use_unicode=True, no_delay=None,
port=None, **kwargs):
if host is None:
host = self.host
if user is None:
Expand All @@ -74,9 +78,12 @@ def create_pool(self, host=None, user=None, password=None,
password = self.password
if db is None:
db = self.db
if port is None:
port = self.port
pool = yield from aiomysql.create_pool(loop=self.loop, host=host,
user=user, password=password,
db=db, use_unicode=use_unicode,
no_delay=no_delay, **kwargs)
no_delay=no_delay, port=port,
**kwargs)
self.addCleanup(pool.close)
return pool
148 changes: 147 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import asyncio
import gc
import os
import ssl
import socket
import sys
import time
import uuid

from docker import APIClient

import aiomysql
import pymysql
import pytest


Expand All @@ -14,11 +21,36 @@
uvloop = None


@pytest.fixture(scope='session')
def unused_port():
def f():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
return f


def pytest_generate_tests(metafunc):
if 'loop_type' in metafunc.fixturenames:
loop_type = ['asyncio', 'uvloop'] if uvloop else ['asyncio']
metafunc.parametrize("loop_type", loop_type)

# if 'mysql_tag' in metafunc.fixturenames:
# tags = set(metafunc.config.option.mysql_tag)
# if not tags:
# tags = ['5.7']
# elif 'all' in tags:
# tags = ['5.6', '5.7', '8.0']
# else:
# tags = list(tags)
# metafunc.parametrize("mysql_tag", tags, scope='session')


# This is here unless someone fixes the generate_tests bit
@pytest.yield_fixture(scope='session')
def mysql_tag():
return '5.6'


@pytest.yield_fixture
def loop(request, loop_type):
Expand Down Expand Up @@ -77,10 +109,19 @@ def pytest_ignore_collect(path, config):
return True


def pytest_addoption(parser):
parser.addoption("--mysql_tag", action="append", default=[],
help=("MySQL server versions. "
"May be used several times. "
"Available values: 5.6, 5.7, 8.0, all"))
parser.addoption("--no-pull", action="store_true", default=False,
help="Don't perform docker images pulling")


@pytest.fixture
def mysql_params():
params = {"host": os.environ.get('MYSQL_HOST', 'localhost'),
"port": os.environ.get('MYSQL_PORT', 3306),
"port": int(os.environ.get('MYSQL_PORT', 3306)),
"user": os.environ.get('MYSQL_USER', 'root'),
"db": os.environ.get('MYSQL_DB', 'test_pymysql'),
"password": os.environ.get('MYSQL_PASSWORD', ''),
Expand Down Expand Up @@ -164,3 +205,108 @@ def _register_table(table_name):
# TODO: probably this is not safe code
sql = "DROP TABLE IF EXISTS {};".format(t)
loop.run_until_complete(cursor.execute(sql))


@pytest.fixture(scope='session')
def session_id():
"""Unique session identifier, random string."""
return str(uuid.uuid4())


@pytest.fixture(scope='session')
def docker():
return APIClient(version='auto')


@pytest.fixture(scope='session')
def mysql_server(unused_port, docker, session_id, mysql_tag, request):
if not request.config.option.no_pull:
docker.pull('mysql:{}'.format(mysql_tag))

# bound IPs do not work on OSX
host = "127.0.0.1"
host_port = unused_port()

# As TLS is optional, might as well always configure it
ssl_directory = os.path.join(os.path.dirname(__file__),
'ssl_resources', 'ssl')
ca_file = os.path.join(ssl_directory, 'ca.pem')
tls_cnf = os.path.join(os.path.dirname(__file__),
'ssl_resources', 'tls.cnf')

ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ctx.check_hostname = False
ctx.load_verify_locations(cafile=ca_file)
# ctx.verify_mode = ssl.CERT_NONE

container_args = dict(
image='mysql:{}'.format(mysql_tag),
name='aiomysql-test-server-{}-{}'.format(mysql_tag, session_id),
ports=[3306],
detach=True,
host_config=docker.create_host_config(
port_bindings={3306: (host, host_port)},
binds={
ssl_directory: {'bind': '/etc/mysql/ssl', 'mode': 'ro'},
tls_cnf: {'bind': '/etc/mysql/conf.d/tls.cnf', 'mode': 'ro'},
}
),
environment={'MYSQL_ROOT_PASSWORD': 'rootpw'}
)

container = docker.create_container(**container_args)

try:
docker.start(container=container['Id'])

# MySQL restarts at least 4 times in the container before its ready
time.sleep(10)

server_params = {
'host': host,
'port': host_port,
'user': 'root',
'password': 'rootpw',
'ssl': ctx
}
delay = 0.001
for i in range(100):
try:
connection = pymysql.connect(
db='mysql',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
**server_params)

with connection.cursor() as cursor:
cursor.execute("SHOW VARIABLES LIKE '%ssl%';")

result = cursor.fetchall()
result = {item['Variable_name']:
item['Value'] for item in result}

assert result['have_ssl'] == "YES", \
"SSL Not Enabled on docker'd MySQL"

cursor.execute("SHOW STATUS LIKE '%Ssl_version%'")

result = cursor.fetchone()
# As we connected with TLS, it should start with that :D
assert result['Value'].startswith('TLS'), \
"Not connected to the database with TLS"

break
except Exception as err:
time.sleep(delay)
delay *= 2
else:
pytest.fail("Cannot start MySQL server")

container['host'] = host
container['port'] = host_port
container['conn_params'] = server_params

yield container
finally:
docker.kill(container=container['Id'])
docker.remove_container(container['Id'])
3 changes: 2 additions & 1 deletion tests/sa/test_sa_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)
self.host = os.environ.get('MYSQL_HOST', 'localhost')
self.port = os.environ.get('MYSQL_PORT', 3306)
self.port = int(os.environ.get('MYSQL_PORT', 3306))
self.user = os.environ.get('MYSQL_USER', 'root')
self.db = os.environ.get('MYSQL_DB', 'test_pymysql')
self.password = os.environ.get('MYSQL_PASSWORD', '')
Expand All @@ -37,6 +37,7 @@ def connect(self, **kwargs):
password=self.password,
host=self.host,
loop=self.loop,
port=self.port,
**kwargs)
yield from conn.autocommit(True)
cur = yield from conn.cursor()
Expand Down
4 changes: 3 additions & 1 deletion tests/sa/test_sa_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)
self.host = os.environ.get('MYSQL_HOST', 'localhost')
self.port = os.environ.get('MYSQL_PORT', 3306)
self.port = int(os.environ.get('MYSQL_PORT', 3306))
self.user = os.environ.get('MYSQL_USER', 'root')
self.db = os.environ.get('MYSQL_DB', 'test_pymysql')
self.password = os.environ.get('MYSQL_PASSWORD', '')
Expand All @@ -38,6 +38,7 @@ def make_engine(self, use_loop=True, **kwargs):
user=self.user,
password=self.password,
host=self.host,
port=self.port,
loop=self.loop,
minsize=10,
**kwargs))
Expand All @@ -46,6 +47,7 @@ def make_engine(self, use_loop=True, **kwargs):
user=self.user,
password=self.password,
host=self.host,
port=self.port,
minsize=10,
**kwargs))

Expand Down
3 changes: 2 additions & 1 deletion tests/sa/test_sa_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)
self.host = os.environ.get('MYSQL_HOST', 'localhost')
self.port = os.environ.get('MYSQL_PORT', 3306)
self.port = int(os.environ.get('MYSQL_PORT', 3306))
self.user = os.environ.get('MYSQL_USER', 'root')
self.db = os.environ.get('MYSQL_DB', 'test_pymysql')
self.password = os.environ.get('MYSQL_PASSWORD', '')
Expand All @@ -58,6 +58,7 @@ def connect(self, **kwargs):
user=self.user,
password=self.password,
host=self.host,
port=self.port,
loop=self.loop,
**kwargs)
# TODO: fix this, should autocommit be enabled by default?
Expand Down
20 changes: 20 additions & 0 deletions tests/ssl_resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# MySQL TLS Stuff

This folder contains some resources to be mounted into a mysql container to support TLS

Most of the instructions were taken from here https://dev.mysql.com/doc/refman/5.7/en/creating-ssl-files-using-openssl.html

# Generating certificates
```bash
openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 3600 -key ca-key.pem -out ca.pem

openssl req -newkey rsa:2048 -days 3600 -nodes -keyout server-key.pem -out server-req.pem
openssl rsa -in server-key.pem -out server-key.pem
openssl x509 -req -in server-req.pem -days 3600 -CA ca.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem
```
The current files under `ssl/` have the default values provided by openssl.

# MySQL Config
MySQL imports all `.cnf` files under `/etc/mysql/conf.d` so a `tls.cnf` is placed in there referencing the SSL CA
cert and server cert and key. The entire `ssl/` directory should be mounted on the container to `/etc/mysql/ssl/`
27 changes: 27 additions & 0 deletions tests/ssl_resources/ssl/ca-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA4QGo8oVPe8HCIqfJXYVxZbHAZDVlMK/e6ypQM463B8lNg6Ce
dd0QfAVIs3wuXtRTVD6ELVFDrmcg/eBjM00Idh2GcI0ojDAPcECBvvk0SWYrUBFn
bou5R1tPwCb8yY601dNExteT61egVhTQd8Vz6NjN9tkMqrpnZ3pn86QqnFWuDOvJ
5YkOAHOjXipkb8ba+Y4Jl50N6KZH2vrHwdWiO/DYQJLuvoOtYI6a+AXNH+ZSNvid
Vz9nb3taUQcNxUlYtTByPfnU9wo/dzDFpNKvC5yScPvh2AmJUrEKNEYkFY/YbqLc
iCXOoBfh1EHqmR2EASjanr+LgwTJfXoX3bQeQQIDAQABAoIBAQDLQ3igPhXjstHy
BKlANwCN4dnvrNzQ8s/qmbsCGHb4Lb48nqkHyMDPiOZ4XkJ1oFH21NMLLVJ7Buci
8cYr3fc63MlKe/qZSgFoYp3TK8U0WXvfRRmvH8Is2CxfZdkPLD/ouoZzKuSRwgMy
QHNi/5kKTHEkAkgTI3muXUHzM+baeknisEqXqCib1yY4FfX2Vnip1dTbbj1gEbGu
vgFM67uXsoKeQ8ykTF5ZUDIphP+tFWgLMZA2L3iTgRSZJztWwxUMa9NZ8erxy25h
HPntC5OELndKEbO+su7wCMkw8w2cNA1V6yyQlwSoIRD1fhit/v4hOmvofTpoikXH
DaCJjgwBAoGBAP84Wq8nrp+79BWRX/qsivERtkGnfjFIDbWBhj70az/CgCV8OWWu
td6VEEl/Gk7IHsrmOySF0EMYYeMoc5WQKMZbb+1x2m7ovxEwS/UGwNGdtZswyOJw
y0LKI6dJPQUiZm/O2m0w1zs6/CvvIfu9QOUTxyvwub4lM8UW/gDy79URAoGBAOGx
q83cTMHKY32dNU/IVaMpw9OxojAWpWXYOEqyV7hM2+gw/lrdBKRyWwB3l3smwet1
FvKINCZ0bTIRbz/UsNtXp8lISTvw5bQhGsQEYx6ncBeBeHN50zSVoR2xBfqu3pQ4
G5V/UI82hba7QUDXkuMJ2T7dcZixLk1vp3y+LvYxAoGBANkGYc7J7qs0F6Xzfeta
p7fA+PuxYxSjEc1DfBWyoDSSv4egr+owe8Tveu8UnxlZAR5GUwqGo4c6h5qzvj3z
XUj3XiFKjJV9Y2RJbn3IpVRaSKDUBi7P/XgpDdJl6/aevv7aplDtlEhwqxjs+zfn
QfTKMbbCuB/h4Lj7CTljW+ARAoGAchB7hgVK/b4t3jRv1yymq1nWUM077RXk7b4D
ZS0RTGH72jO4uW9ugzYQbAIFGwaRh1CcEmNoB+9bqKxLD3WNFK4ObJoN+S9cyFba
0iptdfalnhufJq1xYugkj38CSJnMgBiDSGEZ8+dYWOv2pLDO2dQGadE9MjCJ+DTv
7wmnbmECgYBnkVNH0wOvdMuT0vovm18zqtH04PmQGg5JXgQjtpn/6iC9BjRaF2Nz
Jj9arX00KxIO5vkzbyt5ht5fzk4dpXoG5ozOqnqbj6WNEfgJDeyCkqo1gZOMZSQ/
YPjKGL7rhXbE/FsNEH90nG0NpIAk1ibtD9sYn6LBBYJcuauegOgE7g==
-----END RSA PRIVATE KEY-----
21 changes: 21 additions & 0 deletions tests/ssl_resources/ssl/ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAP2JtVGC0ZPKMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgwMzI5MjEzMzA1WhcNMjgwMjA1MjEzMzA1WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA4QGo8oVPe8HCIqfJXYVxZbHAZDVlMK/e6ypQM463B8lNg6Cedd0QfAVI
s3wuXtRTVD6ELVFDrmcg/eBjM00Idh2GcI0ojDAPcECBvvk0SWYrUBFnbou5R1tP
wCb8yY601dNExteT61egVhTQd8Vz6NjN9tkMqrpnZ3pn86QqnFWuDOvJ5YkOAHOj
Xipkb8ba+Y4Jl50N6KZH2vrHwdWiO/DYQJLuvoOtYI6a+AXNH+ZSNvidVz9nb3ta
UQcNxUlYtTByPfnU9wo/dzDFpNKvC5yScPvh2AmJUrEKNEYkFY/YbqLciCXOoBfh
1EHqmR2EASjanr+LgwTJfXoX3bQeQQIDAQABo1AwTjAdBgNVHQ4EFgQUyNN4RMiv
zhCNNvMta2kOoiw/SXswHwYDVR0jBBgwFoAUyNN4RMivzhCNNvMta2kOoiw/SXsw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAyAiIqfKoemjnJmoMqcrg
N/7QhC9P79rzwKQOEFkCFWJFuGiOD6GhPPCu/9ssrl5vBEwDLdl0V4AeGq8DeKV+
SNON1o6Y6uUAiX5uYK5Asv0avQUu7SS+uvE3YhTELjbvp4vqdLCUjBqq4KZoEA+F
4hXCltPVdOItztzAd7hgktYrkJeDA1M7sZHTv26HaO6vJ0trUdb4tvqzShzMCvN/
2s9ZJAVBZL77Px40yUPiK6cjpq5fGcUen0zBumymBRFOb8ykvq7azUdjk6sz65Vb
Q3kgsKGBHjVOPfXf00YWvgG3NePX3FsBEHtYbBDSD6k+aXQ1WfOUB8mtdwjyEnzD
7w==
-----END CERTIFICATE-----