224 lines
6.9 KiB
Python
224 lines
6.9 KiB
Python
import contextlib
|
|
import docker
|
|
import requests
|
|
from retrying import retry
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy import MetaData
|
|
import testtools
|
|
import threading
|
|
|
|
from oslo_log import log as logging
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
FAKE_DATA_GEN_USERNAME = 'demo'
|
|
FAKE_DATA_GEN_TOKEN = 'demo'
|
|
FAKE_DATA_GEN_PROJECT_ID = 'b9f10eca66ac4c279c139d01e65f96b4'
|
|
|
|
|
|
class DockerSetup(threading.Thread):
|
|
|
|
def __init__(self):
|
|
self.container = None
|
|
self.container_is_ready = threading.Event()
|
|
self.error = None
|
|
self.client = None
|
|
self.repo_dir = './'
|
|
super(DockerSetup, self).__init__()
|
|
|
|
def run(self):
|
|
"""Build a docker container from the given Dockerfile and start
|
|
the container in a separate thread."""
|
|
try:
|
|
self.client = docker.Client(version='auto')
|
|
is_ok = self.client.ping()
|
|
if is_ok != 'OK':
|
|
msg = 'Docker daemon ping failed.'
|
|
self.error = msg
|
|
LOG.error(self.error)
|
|
self.container_is_ready.set()
|
|
return
|
|
except Exception as err:
|
|
self.error = err
|
|
LOG.error(self.error)
|
|
self.container_is_ready.set()
|
|
return
|
|
|
|
# Create Docker image for Craton
|
|
build_output = self.client.build(path=self.repo_dir,
|
|
tag='craton-functional-testing-api',
|
|
dockerfile='Dockerfile',
|
|
pull=True,
|
|
forcerm=True)
|
|
LOG.debug(build_output)
|
|
output_last_line = ""
|
|
for output_last_line in build_output:
|
|
pass
|
|
|
|
message = output_last_line.decode("utf-8")
|
|
if "Successfully built" not in message:
|
|
msg = 'Failed to build docker image.'
|
|
self.error = msg
|
|
self.container_is_ready.set()
|
|
return
|
|
|
|
# create and start the container
|
|
container_tag = 'craton-functional-testing-api'
|
|
self.container = self.client.create_container(container_tag)
|
|
self.client.start(self.container)
|
|
self.container_data = self.client.inspect_container(self.container)
|
|
if self.container_data['State']['Status'] != 'running':
|
|
msg = 'Container is not running.'
|
|
self.error = msg
|
|
self.container_is_ready.set()
|
|
return
|
|
|
|
self.container_is_ready.set()
|
|
|
|
def stop(self):
|
|
"""Stop a running container."""
|
|
if self.container is not None:
|
|
self.client.stop(self.container, timeout=30)
|
|
|
|
def remove(self):
|
|
"""Remove/Delete a stopped container."""
|
|
if self.container is not None:
|
|
self.client.remove_container(self.container)
|
|
|
|
def remove_image(self):
|
|
"""Remove the image we created."""
|
|
if self.client:
|
|
self.client.remove_image('craton-functional-testing-api')
|
|
|
|
|
|
@retry(wait_fixed=1000, stop_max_attempt_number=20)
|
|
def ensure_running_endpoint(container_data):
|
|
service_ip = container_data['NetworkSettings']['IPAddress']
|
|
url = 'http://{}:8080/v1'.format(service_ip)
|
|
headers = {"Content-Type": "application/json"}
|
|
requests.get(url, headers=headers)
|
|
|
|
|
|
_container = None
|
|
|
|
|
|
def setup_container():
|
|
global _container
|
|
|
|
_container = DockerSetup()
|
|
_container.daemon = True
|
|
_container.start()
|
|
_container.container_is_ready.wait()
|
|
|
|
if _container.error:
|
|
teardown_container()
|
|
else:
|
|
try:
|
|
ensure_running_endpoint(_container.container_data)
|
|
except Exception:
|
|
msg = 'Error during data generation script run.'
|
|
_container.error = msg
|
|
teardown_container()
|
|
|
|
|
|
def teardown_container():
|
|
if _container:
|
|
_container.stop()
|
|
_container.remove()
|
|
_container.remove_image()
|
|
|
|
|
|
def setUpModule():
|
|
setup_container()
|
|
|
|
|
|
def tearDownModule():
|
|
teardown_container()
|
|
|
|
|
|
def setup_database(container_ip):
|
|
mysqldb = "mysql+pymysql://craton:craton@{}/craton".format(container_ip)
|
|
engine = create_engine(mysqldb)
|
|
meta = MetaData()
|
|
meta.reflect(engine)
|
|
|
|
with contextlib.closing(engine.connect()) as conn:
|
|
transaction = conn.begin()
|
|
for table in reversed(meta.sorted_tables):
|
|
conn.execute(table.delete())
|
|
transaction.commit()
|
|
|
|
# NOTE(sulo): as a part of db setup, we bootstrap user and project
|
|
# Although, project and user might have been bootstrapped externally
|
|
# we clean the db up for tests, and do our own bootstrapping to
|
|
# isolate all test from any external scripts.
|
|
projects = meta.tables['projects']
|
|
users = meta.tables['users']
|
|
|
|
with contextlib.closing(engine.connect()) as conn:
|
|
transaction = conn.begin()
|
|
conn.execute(projects.insert(),
|
|
name=FAKE_DATA_GEN_USERNAME,
|
|
id=FAKE_DATA_GEN_PROJECT_ID)
|
|
conn.execute(users.insert(),
|
|
project_id=FAKE_DATA_GEN_PROJECT_ID,
|
|
username=FAKE_DATA_GEN_USERNAME,
|
|
api_key=FAKE_DATA_GEN_TOKEN,
|
|
is_admin=True)
|
|
transaction.commit()
|
|
|
|
|
|
class TestCase(testtools.TestCase):
|
|
|
|
def setUp(self):
|
|
"""Base setup provides container data back individual tests."""
|
|
super(TestCase, self).setUp()
|
|
self.container_setup_error = _container.error
|
|
self.session = requests.Session()
|
|
if not self.container_setup_error:
|
|
data = _container.container_data
|
|
self.service_ip = data['NetworkSettings']['IPAddress']
|
|
self.url = 'http://{}:8080'.format(self.service_ip)
|
|
self.session.headers['X-Auth-Project'] = FAKE_DATA_GEN_PROJECT_ID
|
|
self.session.headers['X-Auth-Token'] = FAKE_DATA_GEN_TOKEN
|
|
self.session.headers['X-Auth-User'] = FAKE_DATA_GEN_USERNAME
|
|
|
|
setup_database(self.service_ip)
|
|
|
|
def tearDown(self):
|
|
super(TestCase, self).tearDown()
|
|
|
|
def assertSuccessOk(self, response):
|
|
self.assertEqual(requests.codes.OK, response.status_code)
|
|
|
|
def assertSuccessCreated(self, response):
|
|
self.assertEqual(requests.codes.CREATED, response.status_code)
|
|
|
|
def assertNoContent(self, response):
|
|
self.assertEqual(requests.codes.NO_CONTENT, response.status_code)
|
|
|
|
def get(self, url, headers=None, **params):
|
|
resp = self.session.get(
|
|
url, verify=False, headers=headers, params=params,
|
|
)
|
|
return resp
|
|
|
|
def post(self, url, headers=None, data=None):
|
|
resp = self.session.post(
|
|
url, verify=False, headers=headers, json=data,
|
|
)
|
|
return resp
|
|
|
|
def put(self, url, headers=None, data=None):
|
|
resp = self.session.put(
|
|
url, verify=False, headers=headers, json=data,
|
|
)
|
|
return resp
|
|
|
|
def delete(self, url, headers=None, body=None):
|
|
resp = self.session.delete(
|
|
url, verify=False, headers=headers, json=body,
|
|
)
|
|
return resp
|