Mark repo as obsolete
Change-Id: I58b9861775169397f00e5f72c9cc359d4dcd99d6
This commit is contained in:
parent
1232a9ce53
commit
5ce8f6c0a3
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,11 +0,0 @@
|
||||
.idea
|
||||
*.pyc
|
||||
*.log
|
||||
nosetests.xml
|
||||
*.egg-info
|
||||
/*.egg
|
||||
.tox
|
||||
build
|
||||
dist
|
||||
*.out
|
||||
.coverage
|
@ -1,2 +0,0 @@
|
||||
recursive-include ostf_adapter *.ini
|
||||
recursive-include ostf_adapter *
|
80
README.rst
80
README.rst
@ -1,78 +1,2 @@
|
||||
SETUP:
|
||||
|
||||
|
||||
1. System packages:
|
||||
1.1. Postgres server
|
||||
1.2. libpq-dev
|
||||
2. Install pip-requirements
|
||||
2.1. python setup.py develop
|
||||
Will install python dependencies, and two scripts
|
||||
ostf-server
|
||||
ostf-db
|
||||
3. Migrate postgres database
|
||||
ostf-db --config-file /etc/testing_adapter.conf upgrade head
|
||||
TO REMOVE APPLIED MIGRATION USE:
|
||||
ostf-db --config-file /etc/testing_adapter.conf downgrade -1
|
||||
4. ostf-server --config-file /etc/testing_adapter.conf
|
||||
config-file should be in format of ./etc/testing_adapter.conf.sample
|
||||
where database_connection just usual sqlalchemy url
|
||||
|
||||
|
||||
BY DEFAULT database connection will be:
|
||||
postgresql+psycopg2://adapter:demo@localhost/testing_adapter
|
||||
|
||||
If you want logging to file :
|
||||
ostf-server --log_file testing.log
|
||||
|
||||
After installation hook
|
||||
ostf-server --after-initialization-environment-hook --dbpath=postgresql+psycopg2://postgres:demo@localhost/testing_adapter
|
||||
-------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
USE:
|
||||
|
||||
Design of OSTF REST API entities, urls and output format
|
||||
Testset
|
||||
GET /v1/testsets
|
||||
Response:
|
||||
[
|
||||
{id: "testset-nova-1", name: "Tests for nova"},
|
||||
{id: "testset-keystone-222", name: "Tests for keystone"},
|
||||
...
|
||||
]
|
||||
Test
|
||||
GET /v1/tests
|
||||
Response:
|
||||
[
|
||||
{id: "test_for_adapter.TestSimple.test_first_without_sleep_1", name: "Some test #1", testset: "testset-nova-1"},
|
||||
{id: "test_for_adapter.TestSimple.test_first_without_sleep_2", name: "Some test #2", testset: "testset-nova-1"},
|
||||
{id: "test_for_keystone.TestSimple.fgsfds", name: "Another test", testset: "testset-keystone-222"},
|
||||
...
|
||||
]
|
||||
Testrun (history entry)
|
||||
GET /v1/testruns
|
||||
Response:
|
||||
[
|
||||
{id: <autoincrement>, testset: "testset-keystone-222", metadata: {...}, tests: [
|
||||
{id: "test_for_adapter.TestSimple.test_first_without_sleep_1", status: "running/success/error", message: "error message if error"},
|
||||
...
|
||||
]},
|
||||
...
|
||||
]
|
||||
|
||||
GET /v1/testruns/last/<cluster_id>
|
||||
Response format is the same, but response contains only last entries filtered by cluster_id
|
||||
|
||||
POST /v1/testruns (run the tests)
|
||||
Request:
|
||||
[
|
||||
{testset: "testset-keystone-222", metadata: {...}},
|
||||
...
|
||||
]
|
||||
Response format is like GET reponse format (i.e. with status and id)
|
||||
|
||||
PUT /v1/testruns (stop the tests)
|
||||
Request:
|
||||
[
|
||||
{id: <autoincrement>, status: "stopped"},
|
||||
...
|
||||
]
|
||||
Obsolete repo, please, take a look at fuel-ostf
|
||||
===============================================
|
||||
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,65 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
from ostf_adapter import cli_config
|
||||
from ostf_adapter import nailgun_hooks
|
||||
from ostf_adapter import logger
|
||||
from gevent import pywsgi
|
||||
from ostf_adapter.wsgi import app
|
||||
import logging
|
||||
import signal
|
||||
import pecan
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
cli_args = cli_config.parse_cli()
|
||||
|
||||
config = {
|
||||
'server': {
|
||||
'host': cli_args.host,
|
||||
'port': cli_args.port
|
||||
},
|
||||
'dbpath': cli_args.dbpath,
|
||||
'debug': cli_args.debug,
|
||||
'debug_tests': cli_args.debug_tests
|
||||
}
|
||||
|
||||
logger.setup(log_file=cli_args.log_file)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
root = app.setup_app(config=config)
|
||||
nailgun_hooks.nose_discovery.discovery(cli_args.debug_tests)
|
||||
if getattr(cli_args, 'after_init_hook'):
|
||||
return nailgun_hooks.after_initialization_environment_hook()
|
||||
|
||||
host, port = pecan.conf.server.host, pecan.conf.server.port
|
||||
srv = pywsgi.WSGIServer((host, int(port)), root)
|
||||
|
||||
log.info('Starting server in PID %s', os.getpid())
|
||||
log.info("serving on http://%s:%s", host, port)
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
||||
srv.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,16 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__author__ = 'ekonstantinov'
|
||||
|
@ -1,134 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from functools import wraps
|
||||
from unittest import TestCase
|
||||
|
||||
from ostf_client.client import TestingAdapterClient
|
||||
|
||||
|
||||
class EmptyResponseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Response(object):
|
||||
"""This is testing_adapter response object"""
|
||||
test_name_mapping = {}
|
||||
|
||||
def __init__(self, response):
|
||||
self.is_empty = False
|
||||
if isinstance(response, list):
|
||||
self._parse_json(response)
|
||||
self.request = None
|
||||
else:
|
||||
self._parse_json(response.json())
|
||||
self.request = '{0} {1} \n with {2}'\
|
||||
.format(response.request.method, response.request.url, response.request.body)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item in self.test_sets or item in self._tests:
|
||||
return self.test_sets.get(item) or self._tests.get(item)
|
||||
else:
|
||||
return super(type(self), self).__delattr__(item)
|
||||
|
||||
def __str__(self):
|
||||
if self.is_empty:
|
||||
return "Empty"
|
||||
return self.test_sets.__str__()
|
||||
|
||||
@classmethod
|
||||
def set_test_name_mapping(cls, mapping):
|
||||
cls.test_name_mapping = mapping
|
||||
|
||||
def _parse_json(self, json):
|
||||
if json == [{}]:
|
||||
self.is_empty = True
|
||||
return
|
||||
else:
|
||||
self.is_empty = False
|
||||
|
||||
self.test_sets = {}
|
||||
self._tests = {}
|
||||
for testset in json:
|
||||
self.test_sets[testset.pop('testset')] = testset
|
||||
self._tests = dict((self._friendly_name(item.get('id')), item) for item in testset['tests'])
|
||||
|
||||
def _friendly_name(self, name):
|
||||
return self.test_name_mapping.get(name, name)
|
||||
|
||||
|
||||
class AdapterClientProxy(object):
|
||||
|
||||
def __init__(self, url):
|
||||
self.client = TestingAdapterClient(url)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item in TestingAdapterClient.__dict__:
|
||||
call = getattr(self.client, item)
|
||||
return self._decorate_call(call)
|
||||
def _friendly_map(self, mapping):
|
||||
Response.set_test_name_mapping(mapping)
|
||||
|
||||
def _decorate_call(self, call):
|
||||
@wraps(call)
|
||||
def inner(*args, **kwargs):
|
||||
r = call(*args, **kwargs)
|
||||
return Response(r)
|
||||
return inner
|
||||
|
||||
|
||||
|
||||
|
||||
class SubsetException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseAdapterTest(TestCase):
|
||||
def compare(self, response, comparable):
|
||||
if response.is_empty:
|
||||
msg = '{0} is empty'.format(response.request)
|
||||
raise AssertionError(msg)
|
||||
if not isinstance(comparable, Response):
|
||||
comparable = Response(comparable)
|
||||
test_set = comparable.test_sets.keys()[0]
|
||||
test_set_data = comparable.test_sets[test_set]
|
||||
tests = comparable._tests
|
||||
diff = []
|
||||
|
||||
for item in test_set_data:
|
||||
if item == 'tests':
|
||||
continue
|
||||
if response.test_sets[test_set][item] != test_set_data[item]:
|
||||
msg = 'Actual "{0}" != expected "{1}" in {2}.{3}'.format(response.test_sets[test_set][item],
|
||||
test_set_data[item], test_set, item)
|
||||
diff.append(msg)
|
||||
|
||||
for test_name, test in tests.iteritems():
|
||||
for t in test:
|
||||
if t == 'id':
|
||||
continue
|
||||
if response._tests[test_name][t] != test[t]:
|
||||
msg = 'Actual "{0}" != expected"{1}" in {2}.{3}.{4}'.format(response._tests[test_name][t],
|
||||
test[t], test_set, test_name, t)
|
||||
diff.append(msg)
|
||||
if diff:
|
||||
raise AssertionError(diff)
|
||||
|
||||
@staticmethod
|
||||
def init_client(url, mapping):
|
||||
ac = AdapterClientProxy(url)
|
||||
ac._friendly_map(mapping)
|
||||
return ac
|
||||
|
||||
|
@ -1,108 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__author__ = 'ekonstantinov'
|
||||
CONFIG = {'compute-admin_password': 'nova',
|
||||
'compute-admin_tenant_name': '',
|
||||
'compute-admin_username': '',
|
||||
'compute_allow_tenant_isolation': 'True',
|
||||
'compute_allow_tenant_reuse': 'true',
|
||||
'compute_block_migrate_supports_cinder_iscsi': 'false',
|
||||
'compute_build_interval': '3',
|
||||
'compute_build_timeout': '300',
|
||||
'compute_catalog_type': 'compute',
|
||||
'compute_change_password_available': 'False',
|
||||
'compute_controller_node': '10.30.1.101',
|
||||
'compute_controller_node_name': 'fuel-controller-01.localdomain.',
|
||||
'compute_controller_node_ssh_password': 'r00tme',
|
||||
'compute_controller_node_ssh_user': 'root',
|
||||
'compute_create_image_enabled': 'true',
|
||||
'compute_disk_config_enabled_override': 'true',
|
||||
'compute_enabled_services': 'nova-cert, nova-consoleauth, nova-scheduler, nova-conductor, nova-cert, nova-consoleauth, nova-scheduler, nova-conductor, nova-cert, nova-consoleauth, nova-scheduler, nova-conductor, nova-compute',
|
||||
'compute_fixed_network_name': 'private',
|
||||
'compute_flavor_ref': '1',
|
||||
'compute_flavor_ref_alt': '2',
|
||||
'compute_image_alt_ssh_user': 'cirros',
|
||||
'compute_image_ref': '53734a0d-60a8-4689-b7c8-3c14917a7197',
|
||||
'compute_image_ref_alt': '53734a0d-60a8-4689-b7c8-3c14917a7197',
|
||||
'compute_image_ssh_user': 'cirros',
|
||||
'compute_ip_version_for_ssh': '4',
|
||||
'compute_live_migration_available': 'False',
|
||||
'compute_network_for_ssh': 'private',
|
||||
'compute_resize_available': 'true',
|
||||
'compute_run_ssh': 'false',
|
||||
'compute_ssh_channel_timeout': '60',
|
||||
'compute_ssh_timeout': '300',
|
||||
'compute_ssh_user': 'cirros',
|
||||
'compute_use_block_migration_for_live_migration': 'False',
|
||||
'identity_admin_password': 'nova',
|
||||
'identity_admin_tenant_name': 'admin',
|
||||
'identity_admin_username': 'admin',
|
||||
'identity_alt_password': 'nova',
|
||||
'identity_alt_tenant_name': 'alt_demo',
|
||||
'identity_alt_username': 'alt_demo',
|
||||
'identity_catalog_type': 'identity',
|
||||
'identity_disable_ssl_certificate_validation': 'False',
|
||||
'identity_password': 'nova',
|
||||
'identity_region': 'RegionOne',
|
||||
'identity_strategy': 'keystone',
|
||||
'identity_tenant_name': 'admin',
|
||||
'identity_uri': 'http://172.18.164.70:5000/v2.0/',
|
||||
'identity_url': 'http://172.18.164.70/',
|
||||
'identity_username': 'admin',
|
||||
'image_api_version': '1',
|
||||
'image_catalog_type': 'image',
|
||||
'image_http_image': 'http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz',
|
||||
'network_api_version': '2.0',
|
||||
'network_catalog_type': 'network',
|
||||
'network_public_network_id': 'cdb94175-2002-449f-be41-6b8afce8de13',
|
||||
'network_public_router_id': '2a6bf65b-01f7-4c91-840a-2b5f676e7016',
|
||||
'network_quantum_available': 'true',
|
||||
'network_tenant_network_cidr': '10.13.0.0/16',
|
||||
'network_tenant_network_mask_bits': '28',
|
||||
'network_tenant_networks_reachable': 'true',
|
||||
'object-storage_catalog_type': 'object-store',
|
||||
'object-storage_container_sync_interval': '5',
|
||||
'object-storage_container_sync_timeout': '120',
|
||||
'smoke_allow_tenant_isolation': 'True',
|
||||
'smoke_allow_tenant_reuse': 'true',
|
||||
'smoke_block_migrate_supports_cinder_iscsi': 'false',
|
||||
'smoke_build_interval': '3',
|
||||
'smoke_build_timeout': '300',
|
||||
'smoke_catalog_type': 'compute',
|
||||
'smoke_change_password_available': 'False',
|
||||
'smoke_create_image_enabled': 'true',
|
||||
'smoke_disk_config_enabled_override': 'true',
|
||||
'smoke_fixed_network_name': 'net04',
|
||||
'smoke_flavor_ref': '1',
|
||||
'smoke_flavor_ref_alt': '2',
|
||||
'smoke_image_alt_ssh_user': 'cirros',
|
||||
'smoke_image_ref': '53734a0d-60a8-4689-b7c8-3c14917a7197',
|
||||
'smoke_image_ref_alt': '53734a0d-60a8-4689-b7c8-3c14917a7197',
|
||||
'smoke_image_ssh_user': 'cirros',
|
||||
'smoke_ip_version_for_ssh': '4',
|
||||
'smoke_live_migration_available': 'False',
|
||||
'smoke_network_for_ssh': 'net04',
|
||||
'smoke_resize_available': 'true',
|
||||
'smoke_run_ssh': 'false',
|
||||
'smoke_ssh_channel_timeout': '60',
|
||||
'smoke_ssh_timeout': '320',
|
||||
'smoke_ssh_user': 'cirros',
|
||||
'smoke_use_block_migration_for_live_migration': 'False',
|
||||
'volume_backend1_name': 'BACKEND_1',
|
||||
'volume_backend2_name': 'BACKEND_2',
|
||||
'volume_build_interval': '3',
|
||||
'volume_build_timeout': '300',
|
||||
'volume_catalog_type': 'volume',
|
||||
'volume_multi_backend_enabled': 'false'}
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,33 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__author__ = 'ekonstantinov'
|
||||
from os import environ as env
|
||||
from unittest import TestCase
|
||||
from oslo.config import cfg
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('quantum', default='ALALALALA')
|
||||
]
|
||||
|
||||
class Config(TestCase):
|
||||
def test_config(self):
|
||||
file_path = env['OSTF_CONF_PATH']
|
||||
cfg.CONF
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__profile__ = {
|
||||
"id": "general_test",
|
||||
"driver": "nose",
|
||||
"test_path": "functional/dummy_tests/general_test.py",
|
||||
"description": "General fake tests"
|
||||
}
|
||||
|
||||
import time
|
||||
import httplib
|
||||
import unittest
|
||||
|
||||
|
||||
class Dummy_test(unittest.TestCase):
|
||||
"""Class docstring is required?
|
||||
"""
|
||||
|
||||
def test_fast_pass(self):
|
||||
"""fast pass test
|
||||
This is a simple always pass test
|
||||
Duration: 1sec
|
||||
"""
|
||||
self.assertTrue(True)
|
||||
|
||||
def test_long_pass(self):
|
||||
"""Will sleep 5 sec
|
||||
This is a simple test
|
||||
it will run for 5 sec
|
||||
Duration: 5sec
|
||||
"""
|
||||
time.sleep(5)
|
||||
self.assertTrue(True)
|
||||
|
||||
def test_fast_fail(self):
|
||||
"""Fast fail
|
||||
"""
|
||||
self.assertTrue(False, msg='Something goes wroooong')
|
||||
|
||||
def test_fast_error(self):
|
||||
"""And fast error
|
||||
"""
|
||||
conn = httplib.HTTPSConnection('random.random/random')
|
||||
conn.request("GET", "/random.aspx")
|
||||
|
||||
def test_fail_with_step(self):
|
||||
self.fail('Step 3 Failed: MEssaasasas')
|
@ -1,46 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__profile__ = {
|
||||
"id": "stopped_test",
|
||||
"driver": "nose",
|
||||
"test_path": "functional/dummy_tests/stopped_test.py",
|
||||
"description": "Long running 25 secs fake tests"
|
||||
}
|
||||
|
||||
import time
|
||||
import unittest
|
||||
|
||||
|
||||
class dummy_tests_stopped(unittest.TestCase):
|
||||
|
||||
def test_really_long(self):
|
||||
"""This is long running tests
|
||||
Duration: 25sec
|
||||
"""
|
||||
time.sleep(25)
|
||||
self.assertTrue(True)
|
||||
|
||||
def test_one_no_so_long(self):
|
||||
"""What i am doing here? You ask me????
|
||||
"""
|
||||
time.sleep(5)
|
||||
self.assertFalse(1 == 2)
|
||||
|
||||
def test_not_long_at_all(self):
|
||||
"""You know.. for testing
|
||||
Duration: 1sec
|
||||
"""
|
||||
self.assertTrue(True)
|
||||
|
@ -1,38 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
import pprint
|
||||
|
||||
def make_requests(claster_id, test_set):
|
||||
body = [{'testset': test_set,
|
||||
'metadata': {'config': {'identity_uri': 'hommeee'},
|
||||
'cluster_id': claster_id}
|
||||
}
|
||||
]
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
response = requests.post('http://172.18.164.37:8777/v1/testruns', data=json.dumps(body), headers=headers)
|
||||
pprint.pprint(response.json())
|
||||
_id = response.json()[0]['id']
|
||||
time.sleep(1)
|
||||
body = [{'id': _id, 'status': 'stopped'}]
|
||||
update = requests.put('http://172.18.164.37:8777/v1/testruns', data=json.dumps(body), headers=headers)
|
||||
get_resp = requests.get('http://172.18.164.37:8777/v1/testruns/last/%s' % claster_id)
|
||||
data = get_resp.json()
|
||||
pprint.pprint(data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
make_requests(11, 'fuel_health')
|
@ -1,35 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
import pprint
|
||||
|
||||
def make_requests(claster_id, test_set):
|
||||
tests = ['functional.dummy_tests.general_test.Dummy_test.test_fast_pass',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_error']
|
||||
body = [{'testset': test_set,
|
||||
'tests': tests,
|
||||
'metadata': {
|
||||
'cluster_id': claster_id}}]
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
response = requests.post('http://127.0.0.1:8989/v1/testruns',
|
||||
data=json.dumps(body), headers=headers)
|
||||
pprint.pprint(response.json())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
make_requests('101', 'plugin_general')
|
@ -1,35 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import gevent
|
||||
from gevent import monkey
|
||||
monkey.patch_all()
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
import pprint
|
||||
|
||||
def make_requests(claster_id, test_set):
|
||||
body = [{'testset': test_set,
|
||||
'metadata': {'config': {},
|
||||
'cluster_id': claster_id}}]
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
response = requests.post('http://127.0.0.1:8989/v1/testruns',
|
||||
data=json.dumps(body), headers=headers)
|
||||
pprint.pprint(response.json())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
make_requests('308', 'general_test')
|
@ -1,34 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
import pprint
|
||||
|
||||
def make_requests(claster_id, test_set):
|
||||
tests = ['functional.dummy_tests.general_test.Dummy_test.test_long_pass']
|
||||
body = [{'id': claster_id,
|
||||
'tests': tests,
|
||||
'status': 'restarted',
|
||||
}]
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
response = requests.put('http://127.0.0.1:8989/v1/testruns',
|
||||
data=json.dumps(body), headers=headers)
|
||||
pprint.pprint(response.json())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
make_requests(370, 'plugin_general')
|
@ -1,30 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
import pprint
|
||||
|
||||
def make_requests(claster_id, test_set):
|
||||
body = [{'id': claster_id, 'status': 'stopped'}]
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
update = requests.put(
|
||||
'http://localhost:8989/v1/testruns',
|
||||
data=json.dumps(body), headers=headers)
|
||||
data = update.json()
|
||||
pprint.pprint(data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
make_requests(378, 'plugin_stopped')
|
@ -1,68 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from functional.base import BaseAdapterTest, Response
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class ScenarioTests(BaseAdapterTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
url = 'http://0.0.0.0:8989/v1'
|
||||
mapping = {}
|
||||
|
||||
cls.client = cls.init_client(url, mapping)
|
||||
|
||||
def test_random_scenario(self):
|
||||
testset = "fuel_sanity"
|
||||
cluster_id = 3
|
||||
tests = []
|
||||
timeout = 60
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
for i in range(1):
|
||||
r = self.client.run_with_timeout(testset, tests, cluster_id, timeout)
|
||||
pprint([item for item in r.test_sets[testset]['tests']])
|
||||
if r.fuel_sanity['status'] == 'stopped':
|
||||
running_tests = [test for test in r._tests
|
||||
if r._tests[test]['status'] is 'stopped']
|
||||
print "restarting: ", running_tests
|
||||
result = self.client.restart_with_timeout(testset, running_tests, cluster_id, timeout)
|
||||
print 'Restart', result
|
||||
|
||||
def test_run_fuel_sanity(self):
|
||||
testset = "fuel_sanity"
|
||||
cluster_id = 3
|
||||
tests = []
|
||||
|
||||
timeout = 240
|
||||
|
||||
r = self.client.run_with_timeout(testset, tests, cluster_id, timeout)
|
||||
for item in r.fuel_sanity['tests']:
|
||||
print item['id'].split('.').pop(), item
|
||||
self.assertEqual(r.fuel_sanity['status'], 'finished')
|
||||
|
||||
def test_run_fuel_smoke(self):
|
||||
testset = "fuel_smoke"
|
||||
cluster_id = 3
|
||||
tests = []
|
||||
timeout = 900
|
||||
|
||||
r = self.client.run_with_timeout(testset, tests, cluster_id, timeout)
|
||||
for item in r.fuel_sanity['tests']:
|
||||
print item['id'].split('.').pop(), item
|
||||
self.assertEqual(r.fuel_smoke['status'], 'finished')
|
@ -1,254 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
|
||||
from functional.base import BaseAdapterTest, Response
|
||||
from ostf_client.client import TestingAdapterClient as adapter
|
||||
|
||||
|
||||
class AdapterTests(BaseAdapterTest):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
url = 'http://0.0.0.0:8989/v1'
|
||||
|
||||
cls.mapping = {
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_pass': 'fast_pass',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_error': 'fast_error',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_fail': 'fast_fail',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_long_pass': 'long_pass',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fail_with_step': 'fail_step',
|
||||
'functional.dummy_tests.stopped_test.dummy_tests_stopped.test_really_long': 'really_long',
|
||||
'functional.dummy_tests.stopped_test.dummy_tests_stopped.test_not_long_at_all': 'not_long',
|
||||
'functional.dummy_tests.stopped_test.dummy_tests_stopped.test_one_no_so_long': 'so_long'
|
||||
}
|
||||
cls.testsets = {
|
||||
# "fuel_smoke": None,
|
||||
# "fuel_sanity": None,
|
||||
"general_test": ['fast_pass', 'fast_error', 'fast_fail', 'long_pass'],
|
||||
"stopped_test": ['really_long', 'not_long', 'so_long']
|
||||
}
|
||||
|
||||
cls.adapter = adapter(url)
|
||||
cls.client = cls.init_client(url, cls.mapping)
|
||||
|
||||
def test_list_testsets(self):
|
||||
"""Verify that self.testsets are in json response
|
||||
"""
|
||||
json = self.adapter.testsets().json()
|
||||
response_testsets = [item['id'] for item in json]
|
||||
for testset in self.testsets:
|
||||
msg = '"{test}" not in "{response}"'.format(test=testset, response=response_testsets)
|
||||
self.assertTrue(testset in response_testsets, msg)
|
||||
|
||||
def test_list_tests(self):
|
||||
"""Verify that self.tests are in json response
|
||||
"""
|
||||
json = self.adapter.tests().json()
|
||||
response_tests = [item['id'] for item in json]
|
||||
|
||||
for test in self.mapping:
|
||||
msg = '"{test}" not in "{response}"'.format(test=test.capitalize(), response=response_tests)
|
||||
self.assertTrue(test in response_tests, msg)
|
||||
|
||||
def test_run_testset(self):
|
||||
"""Verify that test status changes in time from running to success
|
||||
"""
|
||||
testset = "general_test"
|
||||
cluster_id = 1
|
||||
|
||||
self.client.start_testrun(testset, cluster_id)
|
||||
time.sleep(3)
|
||||
|
||||
r = self.client.testruns_last(cluster_id)
|
||||
|
||||
assertions = Response([{'status': 'running',
|
||||
'testset': 'general_test',
|
||||
'tests': [
|
||||
{'id': 'fast_pass', 'status': 'success', 'name': 'fast pass test',
|
||||
'description': """ This is a simple always pass test
|
||||
""",},
|
||||
{'id': 'long_pass', 'status': 'running'},
|
||||
{'id': 'fail_step', 'message': 'MEssaasasas', 'status': 'failure'},
|
||||
{'id': 'fast_error', 'message': '', 'status': 'error'},
|
||||
{'id': 'fast_fail', 'message': 'Something goes wroooong', 'status': 'failure'}]}])
|
||||
|
||||
print r
|
||||
print assertions
|
||||
|
||||
self.compare(r, assertions)
|
||||
time.sleep(10)
|
||||
|
||||
r = self.client.testruns_last(cluster_id)
|
||||
|
||||
assertions.general_test['status'] = 'finished'
|
||||
assertions.long_pass['status'] = 'success'
|
||||
|
||||
self.compare(r, assertions)
|
||||
|
||||
def test_stop_testset(self):
|
||||
"""Verify that long running testrun can be stopped
|
||||
"""
|
||||
testset = "stopped_test"
|
||||
cluster_id = 2
|
||||
|
||||
self.client.start_testrun(testset, cluster_id)
|
||||
time.sleep(10)
|
||||
r = self.client.testruns_last(cluster_id)
|
||||
assertions = Response([
|
||||
{'status': 'running',
|
||||
'testset': 'stopped_test',
|
||||
'tests': [
|
||||
{'id': 'not_long', 'status': 'success'},
|
||||
{'id': 'so_long', 'status': 'success'},
|
||||
{'id': 'really_long', 'status': 'running'}]}])
|
||||
|
||||
self.compare(r, assertions)
|
||||
|
||||
self.client.stop_testrun_last(testset, cluster_id)
|
||||
r = self.client.testruns_last(cluster_id)
|
||||
|
||||
assertions.stopped_test['status'] = 'finished'
|
||||
assertions.really_long['status'] = 'stopped'
|
||||
self.compare(r, assertions)
|
||||
|
||||
def test_cant_start_while_running(self):
|
||||
"""Verify that you can't start new testrun for the same cluster_id while previous run is running"""
|
||||
testsets = {"stopped_test": None,
|
||||
"general_test": None}
|
||||
cluster_id = 3
|
||||
|
||||
for testset in testsets:
|
||||
self.client.start_testrun(testset, cluster_id)
|
||||
self.client.testruns_last(cluster_id)
|
||||
|
||||
for testset in testsets:
|
||||
r = self.client.start_testrun(testset, cluster_id)
|
||||
|
||||
msg = "Response {0} is not empty when you try to start testrun" \
|
||||
" with testset and cluster_id that are already running".format(r)
|
||||
|
||||
self.assertTrue(r.is_empty, msg)
|
||||
|
||||
def test_start_many_runs(self):
|
||||
"""Verify that you can start 20 testruns in a row with different cluster_id"""
|
||||
testset = "general_test"
|
||||
|
||||
for cluster_id in range(100, 105):
|
||||
r = self.client.start_testrun(testset, cluster_id)
|
||||
msg = '{0} was empty'.format(r.request)
|
||||
self.assertFalse(r.is_empty, msg)
|
||||
|
||||
'''TODO: Rewrite assertions to verity that all 5 testruns ended with appropriate status'''
|
||||
|
||||
def test_run_single_test(self):
|
||||
"""Verify that you can run individual tests from given testset"""
|
||||
testset = "general_test"
|
||||
tests = ['functional.dummy_tests.general_test.Dummy_test.test_fast_pass',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_fail']
|
||||
cluster_id = 50
|
||||
|
||||
r = self.client.start_testrun_tests(testset, tests, cluster_id)
|
||||
assertions = Response([
|
||||
{'status': 'running',
|
||||
'testset': 'general_test',
|
||||
'tests': [
|
||||
{'status': 'disabled', 'id': 'fast_error'},
|
||||
{'status': 'wait_running', 'id': 'fast_fail'},
|
||||
{'status': 'wait_running', 'id': 'fast_pass'},
|
||||
{'status': 'disabled', 'id': 'long_pass'}]}])
|
||||
|
||||
self.compare(r, assertions)
|
||||
time.sleep(2)
|
||||
|
||||
r = self.client.testruns_last(cluster_id)
|
||||
assertions.general_test['status'] = 'finished'
|
||||
assertions.fast_fail['status'] = 'failure'
|
||||
assertions.fast_pass['status'] = 'success'
|
||||
self.compare(r, assertions)
|
||||
|
||||
def test_single_test_restart(self):
|
||||
"""Verify that you restart individual tests for given testrun"""
|
||||
testset = "general_test"
|
||||
tests = ['functional.dummy_tests.general_test.Dummy_test.test_fast_pass',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_fail']
|
||||
cluster_id = 60
|
||||
|
||||
self.client.run_testset_with_timeout(testset, cluster_id, 10)
|
||||
|
||||
r = self.client.restart_tests_last(testset, tests, cluster_id)
|
||||
assertions = Response([
|
||||
{'status': 'running',
|
||||
'testset': 'general_test',
|
||||
'tests': [
|
||||
{'id': 'fast_pass', 'status': 'wait_running'},
|
||||
{'id': 'long_pass', 'status': 'success'},
|
||||
{'id': 'fast_error', 'status': 'error'},
|
||||
{'id': 'fast_fail', 'status': 'wait_running'}]}])
|
||||
|
||||
self.compare(r, assertions)
|
||||
time.sleep(5)
|
||||
|
||||
r = self.client.testruns_last(cluster_id)
|
||||
assertions.general_test['status'] = 'finished'
|
||||
assertions.fast_pass['status'] = 'success'
|
||||
assertions.fast_fail['status'] = 'failure'
|
||||
|
||||
self.compare(r, assertions)
|
||||
|
||||
def test_restart_combinations(self):
|
||||
"""Verify that you can restart both tests that ran and did not run during single test start"""
|
||||
testset = "general_test"
|
||||
tests = ['functional.dummy_tests.general_test.Dummy_test.test_fast_pass',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_fail']
|
||||
disabled_test = ['functional.dummy_tests.general_test.Dummy_test.test_fast_error', ]
|
||||
cluster_id = 70
|
||||
|
||||
self.client.run_with_timeout(testset, tests, cluster_id, 70)
|
||||
self.client.restart_with_timeout(testset, tests, cluster_id, 10)
|
||||
|
||||
r = self.client.restart_tests_last(testset, disabled_test, cluster_id)
|
||||
assertions = Response([
|
||||
{'status': 'running',
|
||||
'testset': 'general_test',
|
||||
'tests': [
|
||||
{'status': 'wait_running', 'id': 'fast_error'},
|
||||
{'status': 'failure', 'id': 'fast_fail'},
|
||||
{'status': 'success', 'id': 'fast_pass'},
|
||||
{'status': 'disabled', 'id': 'long_pass'}]}])
|
||||
print r
|
||||
self.compare(r, assertions)
|
||||
time.sleep(5)
|
||||
|
||||
r = self.client.testruns_last(cluster_id)
|
||||
assertions.general_test['status'] = 'finished'
|
||||
assertions.fast_error['status'] = 'error'
|
||||
self.compare(r, assertions)
|
||||
|
||||
def test_cant_restart_during_run(self):
|
||||
testset = 'general_test'
|
||||
tests = ['functional.dummy_tests.general_test.Dummy_test.test_fast_pass',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_fail',
|
||||
'functional.dummy_tests.general_test.Dummy_test.test_fast_pass']
|
||||
cluster_id = 999
|
||||
|
||||
self.client.start_testrun(testset, cluster_id)
|
||||
time.sleep(2)
|
||||
|
||||
r = self.client.restart_tests_last(testset, tests, cluster_id)
|
||||
msg = 'Response was not empty after trying to restart running testset:\n {0}'.format(r.request)
|
||||
self.assertTrue(r.is_empty, msg)
|
||||
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,34 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
def parse_cli():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--after-initialization-environment-hook',
|
||||
action='store_true', dest='after_init_hook')
|
||||
parser.add_argument('--debug',
|
||||
action='store_true', dest='debug')
|
||||
parser.add_argument(
|
||||
'--dbpath', metavar='DB_PATH',
|
||||
default='postgresql+psycopg2://adapter:demo@localhost/testing_adapter')
|
||||
parser.add_argument('--host', default='127.0.0.1')
|
||||
parser.add_argument('--port', default='8989')
|
||||
parser.add_argument('--log_file', default=None, metavar='PATH')
|
||||
parser.add_argument('--nailgun-host', default='127.0.0.1')
|
||||
parser.add_argument('--nailgun-port', default='3232')
|
||||
parser.add_argument('--debug_tests', default=None)
|
||||
return parser.parse_args(sys.argv[1:])
|
@ -1,38 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import logging
|
||||
import logging.handlers
|
||||
|
||||
|
||||
def setup(log_file=None):
|
||||
formatter = logging.Formatter(
|
||||
'[%(asctime)s] %(levelname)-8s %(message)s')
|
||||
log = logging.getLogger(None)
|
||||
stream_handler = logging.StreamHandler()
|
||||
stream_handler.setLevel(logging.INFO)
|
||||
stream_handler.setFormatter(formatter)
|
||||
log.addHandler(stream_handler)
|
||||
|
||||
if log_file:
|
||||
log_file = os.path.abspath(log_file)
|
||||
file_handler = logging.handlers.WatchedFileHandler(log_file)
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
mode = int('0644', 8)
|
||||
os.chmod(log_file, mode)
|
||||
file_handler.setFormatter(formatter)
|
||||
log.addHandler(file_handler)
|
||||
|
||||
log.setLevel(logging.INFO)
|
@ -1,29 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
from ostf_adapter.storage import alembic_cli
|
||||
from ostf_adapter.nose_plugin import nose_discovery
|
||||
from pecan import conf
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def after_initialization_environment_hook():
|
||||
"""Expect 0 on success by nailgun
|
||||
Exception is good enough signal that something goes wrong
|
||||
"""
|
||||
alembic_cli.do_apply_migrations()
|
||||
nose_discovery.discovery(conf.debug_tests)
|
||||
return 0
|
@ -1,31 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from stevedore import extension
|
||||
|
||||
|
||||
_PLUGIN_MANAGER = None
|
||||
|
||||
|
||||
def get_plugin(plugin):
|
||||
global _PLUGIN_MANAGER
|
||||
plugin_manager = _PLUGIN_MANAGER
|
||||
|
||||
if plugin_manager is None:
|
||||
PLUGINS_NAMESPACE = 'plugins'
|
||||
plugin_manager = extension.ExtensionManager(PLUGINS_NAMESPACE,
|
||||
invoke_on_load=True)
|
||||
|
||||
_PLUGIN_MANAGER = plugin_manager
|
||||
return _PLUGIN_MANAGER[plugin].obj
|
@ -1,108 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from pecan import conf
|
||||
|
||||
from ostf_adapter import storage
|
||||
from ostf_adapter.nose_plugin import nose_utils
|
||||
from ostf_adapter.nose_plugin import nose_storage_plugin
|
||||
from ostf_adapter.nose_plugin import nose_test_runner
|
||||
from ostf_adapter.storage import engine, models
|
||||
from ostf_adapter.storage import storage_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NoseDriver(object):
|
||||
def __init__(self):
|
||||
LOG.warning('WTF')
|
||||
self._named_threads = {}
|
||||
session = engine.get_session()
|
||||
with session.begin(subtransactions=True):
|
||||
storage_utils.update_all_running_test_runs(session)
|
||||
|
||||
def check_current_running(self, unique_id):
|
||||
return unique_id in self._named_threads
|
||||
|
||||
def run(self, test_run, test_set, tests=None):
|
||||
"""
|
||||
remove unneceserry arguments
|
||||
spawn processes and send them tasks as to workers
|
||||
"""
|
||||
tests = tests or test_run.enabled_tests
|
||||
if tests:
|
||||
argv_add = [nose_utils.modify_test_name_for_nose(test) for test in
|
||||
tests]
|
||||
else:
|
||||
argv_add = [test_set.test_path] + test_set.additional_arguments
|
||||
|
||||
self._named_threads[test_run.id] = nose_utils.run_proc(
|
||||
self._run_tests, test_run.id, test_run.cluster_id, argv_add)
|
||||
|
||||
def _run_tests(self, test_run_id, cluster_id, argv_add):
|
||||
session = engine.get_session()
|
||||
try:
|
||||
nose_test_runner.SilentTestProgram(
|
||||
addplugins=[nose_storage_plugin.StoragePlugin(
|
||||
test_run_id, str(cluster_id))],
|
||||
exit=False,
|
||||
argv=['ostf_tests'] + argv_add)
|
||||
self._named_threads.pop(int(test_run_id), None)
|
||||
except Exception, e:
|
||||
LOG.exception('Test run: %s\n', test_run_id)
|
||||
finally:
|
||||
models.TestRun.update_test_run(
|
||||
session, test_run_id, status='finished')
|
||||
|
||||
def kill(self, test_run_id, cluster_id, cleanup=None):
|
||||
session = engine.get_session()
|
||||
if test_run_id in self._named_threads:
|
||||
|
||||
self._named_threads[test_run_id].terminate()
|
||||
self._named_threads.pop(test_run_id, None)
|
||||
|
||||
if cleanup:
|
||||
nose_utils.run_proc(
|
||||
self._clean_up,
|
||||
test_run_id,
|
||||
cluster_id,
|
||||
cleanup)
|
||||
else:
|
||||
models.TestRun.update_test_run(
|
||||
session, test_run_id, status='finished')
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
def _clean_up(self, test_run_id, external_id, cleanup):
|
||||
session = engine.get_session()
|
||||
try:
|
||||
module_obj = __import__(cleanup, -1)
|
||||
|
||||
os.environ['NAILGUN_HOST'] = str(conf.nailgun.host)
|
||||
os.environ['NAILGUN_PORT'] = str(conf.nailgun.port)
|
||||
os.environ['CLUSTER_ID'] = str(external_id)
|
||||
|
||||
module_obj.cleanup.cleanup()
|
||||
|
||||
except Exception:
|
||||
LOG.exception('EXCEPTION IN CLEANUP')
|
||||
|
||||
finally:
|
||||
models.TestRun.update_test_run(
|
||||
session, test_run_id, status='finished')
|
@ -1,88 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from nose import plugins
|
||||
import logging
|
||||
import os
|
||||
|
||||
from ostf_adapter.nose_plugin import nose_test_runner
|
||||
from ostf_adapter.nose_plugin import nose_utils
|
||||
from ostf_adapter.storage import engine, models
|
||||
|
||||
|
||||
CORE_PATH = 'fuel_health'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DiscoveryPlugin(plugins.Plugin):
|
||||
|
||||
enabled = True
|
||||
name = 'discovery'
|
||||
score = 15000
|
||||
|
||||
def __init__(self):
|
||||
self.test_sets = {}
|
||||
super(DiscoveryPlugin, self).__init__()
|
||||
|
||||
def options(self, parser, env=os.environ):
|
||||
pass
|
||||
|
||||
def configure(self, options, conf):
|
||||
pass
|
||||
|
||||
def afterImport(self, filename, module):
|
||||
module = __import__(module, fromlist=[module])
|
||||
LOG.info('Inspecting %s', filename)
|
||||
if hasattr(module, '__profile__'):
|
||||
session = engine.get_session()
|
||||
with session.begin(subtransactions=True):
|
||||
LOG.info('%s discovered.', module.__name__)
|
||||
test_set = models.TestSet(**module.__profile__)
|
||||
test_set = session.merge(test_set)
|
||||
session.add(test_set)
|
||||
self.test_sets[test_set.id] = test_set
|
||||
|
||||
def addSuccess(self, test):
|
||||
test_id = test.id()
|
||||
for test_set_id in self.test_sets.keys():
|
||||
if test_set_id in test_id:
|
||||
session = engine.get_session()
|
||||
with session.begin(subtransactions=True):
|
||||
LOG.info('%s added for %s', test_id, test_set_id)
|
||||
data = dict()
|
||||
data['title'], data['description'], data['duration'] = \
|
||||
nose_utils.get_description(test)
|
||||
old_test_obj = session.query(models.Test).filter_by(
|
||||
name=test_id, test_set_id=test_set_id,
|
||||
test_run_id=None).\
|
||||
update(data, synchronize_session=False)
|
||||
if not old_test_obj:
|
||||
data.update({'test_set_id': test_set_id,
|
||||
'name': test_id})
|
||||
test_obj = models.Test(**data)
|
||||
session.add(test_obj)
|
||||
|
||||
|
||||
def discovery(path=None):
|
||||
"""
|
||||
function to automaticly discover any test packages
|
||||
"""
|
||||
|
||||
tests = [CORE_PATH, path] if path else [CORE_PATH]
|
||||
LOG.info('Starting discovery for %r.', tests)
|
||||
nose_test_runner.SilentTestProgram(
|
||||
addplugins=[DiscoveryPlugin()],
|
||||
exit=False,
|
||||
argv=['tests_discovery', '--collect-only'] + tests)
|
@ -1,112 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from time import time
|
||||
import logging
|
||||
import os
|
||||
|
||||
from nose import plugins
|
||||
from nose.suite import ContextSuite
|
||||
from pecan import conf
|
||||
|
||||
from ostf_adapter.nose_plugin import nose_utils
|
||||
from ostf_adapter.storage import models
|
||||
from ostf_adapter.storage import engine
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StoragePlugin(plugins.Plugin):
|
||||
enabled = True
|
||||
name = 'storage'
|
||||
score = 15000
|
||||
|
||||
def __init__(
|
||||
self, test_run_id, cluster_id):
|
||||
self.test_run_id = test_run_id
|
||||
self.cluster_id = cluster_id
|
||||
super(StoragePlugin, self).__init__()
|
||||
self._start_time = None
|
||||
|
||||
def options(self, parser, env=os.environ):
|
||||
env['NAILGUN_HOST'] = str(conf.nailgun.host)
|
||||
env['NAILGUN_PORT'] = str(conf.nailgun.port)
|
||||
if self.cluster_id:
|
||||
env['CLUSTER_ID'] = str(self.cluster_id)
|
||||
|
||||
def configure(self, options, conf):
|
||||
self.conf = conf
|
||||
|
||||
def _add_message(
|
||||
self, test, err=None, status=None):
|
||||
data = {
|
||||
'status': status,
|
||||
'time_taken': self.taken
|
||||
}
|
||||
data['title'], data['description'], data['duration'] = \
|
||||
nose_utils.get_description(test)
|
||||
if err:
|
||||
exc_type, exc_value, exc_traceback = err
|
||||
data['step'], data['message'] = None, u''
|
||||
if not status == 'error':
|
||||
data['step'], data['message'] = \
|
||||
nose_utils.format_failure_message(exc_value)
|
||||
data['traceback'] = u''
|
||||
else:
|
||||
data['step'], data['message'] = None, u''
|
||||
data['traceback'] = u''
|
||||
|
||||
session = engine.get_session()
|
||||
|
||||
with session.begin(subtransactions=True):
|
||||
|
||||
if isinstance(test, ContextSuite):
|
||||
for sub_test in test._tests:
|
||||
data['title'], data['description'], data['duration'] = \
|
||||
nose_utils.get_description(test)
|
||||
models.Test.add_result(
|
||||
session, self.test_run_id, sub_test.id(), data)
|
||||
else:
|
||||
models.Test.add_result(
|
||||
session, self.test_run_id, test.id(), data)
|
||||
|
||||
def addSuccess(self, test, capt=None):
|
||||
self._add_message(test, status='success')
|
||||
|
||||
def addFailure(self, test, err):
|
||||
LOG.error('%s', test.id(), exc_info=err)
|
||||
self._add_message(test, err=err, status='failure')
|
||||
|
||||
def addError(self, test, err):
|
||||
if err[0] == AssertionError:
|
||||
LOG.error('%s', test.id(), exc_info=err)
|
||||
self._add_message(
|
||||
test, err=err, status='failure')
|
||||
else:
|
||||
LOG.error('%s', test.id(), exc_info=err)
|
||||
self._add_message(test, err=err, status='error')
|
||||
|
||||
def beforeTest(self, test):
|
||||
self._start_time = time()
|
||||
self._add_message(test, status='running')
|
||||
|
||||
def describeTest(self, test):
|
||||
return test.test._testMethodDoc
|
||||
|
||||
@property
|
||||
def taken(self):
|
||||
if self._start_time:
|
||||
return time() - self._start_time
|
||||
return 0
|
@ -1,36 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from nose import core
|
||||
|
||||
|
||||
class SilentTestRunner(core.TextTestRunner):
|
||||
def run(self, test):
|
||||
"""Overrides to provide plugin hooks and defer all output to
|
||||
the test result class.
|
||||
"""
|
||||
result = self._makeResult()
|
||||
test(result)
|
||||
return result
|
||||
|
||||
|
||||
class SilentTestProgram(core.TestProgram):
|
||||
def runTests(self):
|
||||
"""Run Tests. Returns true on success, false on failure, and sets
|
||||
self.success to the same value.
|
||||
"""
|
||||
self.testRunner = SilentTestRunner(stream=self.config.stream,
|
||||
verbosity=0,
|
||||
config=self.config)
|
||||
return self.testRunner.run(self.test).wasSuccessful()
|
@ -1,102 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import traceback
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import multiprocessing
|
||||
|
||||
from nose import case
|
||||
|
||||
|
||||
def parse_json_file(file_path):
|
||||
current_directory = os.path.dirname(os.path.realpath(__file__))
|
||||
commands_path = os.path.join(
|
||||
current_directory, file_path)
|
||||
with open(commands_path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def get_exc_message(exception_value):
|
||||
"""
|
||||
@exception_value - Exception type object
|
||||
"""
|
||||
_exc_long = str(exception_value)
|
||||
if isinstance(_exc_long, basestring):
|
||||
return _exc_long.split('\n')[0]
|
||||
return u""
|
||||
|
||||
|
||||
def get_description(test_obj):
|
||||
if isinstance(test_obj, case.Test):
|
||||
docstring = test_obj.shortDescription()
|
||||
|
||||
if docstring:
|
||||
duration_pattern = r'Duration:.?(?P<duration>.+)'
|
||||
duration_matcher = re.search(duration_pattern, docstring)
|
||||
if duration_matcher:
|
||||
duration = duration_matcher.group(1)
|
||||
docstring = docstring[:duration_matcher.start()]
|
||||
else:
|
||||
duration = None
|
||||
docstring = docstring.split('\n')
|
||||
name = docstring.pop(0)
|
||||
description = u'\n'.join(docstring) if docstring else u""
|
||||
|
||||
return name, description, duration
|
||||
return u"", u"", u""
|
||||
|
||||
|
||||
def modify_test_name_for_nose(test_path):
|
||||
test_module, test_class, test_method = test_path.rsplit('.', 2)
|
||||
return '{0}:{1}.{2}'.format(test_module, test_class, test_method)
|
||||
|
||||
|
||||
def format_exception(exc_info):
|
||||
ec, ev, tb = exc_info
|
||||
|
||||
# formatError() may have turned our exception object into a string, and
|
||||
# Python 3's traceback.format_exception() doesn't take kindly to that (it
|
||||
# expects an actual exception object). So we work around it, by doing the
|
||||
# work ourselves if ev is a string.
|
||||
if isinstance(ev, basestring):
|
||||
tb_data = ''.join(traceback.format_tb(tb))
|
||||
return tb_data + ev
|
||||
else:
|
||||
return ''.join(traceback.format_exception(*exc_info))
|
||||
|
||||
|
||||
def format_failure_message(message):
|
||||
message = get_exc_message(message)
|
||||
matcher = re.search(
|
||||
r'^[a-zA-Z]+\s?(\d+)\s?[a-zA-Z]+\s?[\.:]\s?(.+)',
|
||||
message)
|
||||
if matcher:
|
||||
step, msg = matcher.groups()
|
||||
return int(step), msg
|
||||
return None, message
|
||||
|
||||
|
||||
def run_proc(func, *args):
|
||||
proc = multiprocessing.Process(
|
||||
target=func,
|
||||
args=args)
|
||||
proc.daemon = True
|
||||
proc.start()
|
||||
return proc
|
||||
|
||||
|
||||
def get_module(module_path):
|
||||
pass
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,49 +0,0 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = migrations
|
||||
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
sqlalchemy.url = postgresql+psycopg2://ostf:ostf@localhost/ostf
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
@ -1,32 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from alembic import command, config
|
||||
from pecan import conf
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def do_apply_migrations():
|
||||
alembic_conf = config.Config(
|
||||
os.path.join(os.path.dirname(__file__), 'alembic.ini')
|
||||
)
|
||||
alembic_conf.set_main_option('script_location',
|
||||
'ostf_adapter.storage:migrations')
|
||||
alembic_conf.set_main_option('sqlalchemy.url', conf.dbpath)
|
||||
command.upgrade(alembic_conf, 'head')
|
@ -1,57 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from pecan import conf
|
||||
from sqlalchemy import create_engine, orm, pool
|
||||
|
||||
|
||||
_ENGINE = None
|
||||
_MAKER = None
|
||||
|
||||
|
||||
def get_session(autocommit=True, expire_on_commit=False):
|
||||
"""Return a SQLAlchemy session."""
|
||||
global _MAKER
|
||||
global _SLAVE_MAKER
|
||||
maker = _MAKER
|
||||
|
||||
if maker is None:
|
||||
engine = get_engine()
|
||||
maker = get_maker(engine, autocommit, expire_on_commit)
|
||||
|
||||
else:
|
||||
_MAKER = maker
|
||||
|
||||
session = maker()
|
||||
return session
|
||||
|
||||
|
||||
def get_engine(pool_type=None):
|
||||
"""Return a SQLAlchemy engine."""
|
||||
global _ENGINE
|
||||
engine = _ENGINE
|
||||
|
||||
if engine is None:
|
||||
engine = create_engine(conf.dbpath,
|
||||
poolclass=pool_type or pool.NullPool)
|
||||
_ENGINE = engine
|
||||
return engine
|
||||
|
||||
|
||||
def get_maker(engine, autocommit=True, expire_on_commit=False):
|
||||
"""Return a SQLAlchemy sessionmaker using the given engine."""
|
||||
return orm.sessionmaker(
|
||||
bind=engine,
|
||||
autocommit=autocommit,
|
||||
expire_on_commit=expire_on_commit)
|
@ -1,28 +0,0 @@
|
||||
import json
|
||||
|
||||
from sqlalchemy.types import TypeDecorator, VARCHAR
|
||||
|
||||
|
||||
class JsonField(TypeDecorator):
|
||||
impl = VARCHAR
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value is not None:
|
||||
value = json.dumps(value)
|
||||
|
||||
return value
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
if value is not None:
|
||||
value = json.loads(value)
|
||||
return value
|
||||
|
||||
|
||||
class ListField(JsonField):
|
||||
def process_bind_param(self, value, dialect):
|
||||
value = list(value) if value else []
|
||||
super(ListField, self).process_bind_param(value, dialect)
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
value = super(ListField, self).process_bind_param(value, dialect)
|
||||
return list(value) if value else []
|
@ -1 +0,0 @@
|
||||
Generic single-database configuration.
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,91 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import with_statement
|
||||
from logging.config import fileConfig
|
||||
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from ostf_adapter.storage import models
|
||||
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
target_metadata = models.BASE.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(url=url)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
engine = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool)
|
||||
|
||||
connection = engine.connect()
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata
|
||||
)
|
||||
|
||||
try:
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
@ -1,22 +0,0 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
@ -1,42 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Add status field to test run model
|
||||
|
||||
Revision ID: 12340edd992d
|
||||
Revises: 1e2c38f575fb
|
||||
Create Date: 2013-07-03 17:38:42.632146
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '12340edd992d'
|
||||
down_revision = '1e2c38f575fb'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
'test_runs',
|
||||
sa.Column('status', sa.String(length=128), nullable=True))
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('test_runs', 'status')
|
||||
### end Alembic commands ###
|
@ -1,93 +0,0 @@
|
||||
"""Database refactoring
|
||||
|
||||
Revision ID: 1b28f7bc6476
|
||||
Revises: 4e9905279776
|
||||
Create Date: 2013-08-07 13:20:06.373200
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1b28f7bc6476'
|
||||
down_revision = '4e9905279776'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
from ostf_adapter.storage import fields
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('test_runs', sa.Column('test_set_id', sa.String(length=128),
|
||||
nullable=True))
|
||||
op.add_column('test_runs',
|
||||
sa.Column('meta', fields.JsonField(), nullable=True))
|
||||
op.add_column('test_runs',
|
||||
sa.Column('cluster_id', sa.Integer(), nullable=False))
|
||||
op.drop_column('test_runs', u'type')
|
||||
op.drop_column('test_runs', u'stats')
|
||||
op.drop_column('test_runs', u'external_id')
|
||||
op.drop_column('test_runs', u'data')
|
||||
op.alter_column('test_runs', 'status',
|
||||
existing_type=sa.VARCHAR(length=128),
|
||||
nullable=False)
|
||||
op.add_column('test_sets', sa.Column('cleanup_path', sa.String(length=128),
|
||||
nullable=True))
|
||||
op.add_column('test_sets',
|
||||
sa.Column('meta', fields.JsonField(), nullable=True))
|
||||
op.add_column('test_sets',
|
||||
sa.Column('driver', sa.String(length=128), nullable=True))
|
||||
op.add_column('test_sets',
|
||||
sa.Column('additional_arguments', fields.ListField(),
|
||||
nullable=True))
|
||||
op.add_column('test_sets',
|
||||
sa.Column('test_path', sa.String(length=256), nullable=True))
|
||||
op.drop_column('test_sets', u'data')
|
||||
op.add_column('tests', sa.Column('description', sa.Text(), nullable=True))
|
||||
op.add_column('tests', sa.Column('traceback', sa.Text(), nullable=True))
|
||||
op.add_column('tests', sa.Column('step', sa.Integer(), nullable=True))
|
||||
op.add_column('tests', sa.Column('meta', fields.JsonField(),
|
||||
nullable=True))
|
||||
op.add_column('tests',
|
||||
sa.Column('duration', sa.String(length=512), nullable=True))
|
||||
op.add_column('tests', sa.Column('message', sa.Text(), nullable=True))
|
||||
op.add_column('tests',
|
||||
sa.Column('time_taken', sa.Float(), nullable=True))
|
||||
op.drop_column('tests', u'taken')
|
||||
op.drop_column('tests', u'data')
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('tests', sa.Column(u'data', sa.TEXT(), nullable=True))
|
||||
op.add_column('tests', sa.Column(u'taken',
|
||||
postgresql.DOUBLE_PRECISION(precision=53),
|
||||
nullable=True))
|
||||
op.drop_column('tests', 'time_taken')
|
||||
op.drop_column('tests', 'message')
|
||||
op.drop_column('tests', 'duration')
|
||||
op.drop_column('tests', 'meta')
|
||||
op.drop_column('tests', 'step')
|
||||
op.drop_column('tests', 'traceback')
|
||||
op.drop_column('tests', 'description')
|
||||
op.add_column('test_sets', sa.Column(u'data', sa.TEXT(), nullable=True))
|
||||
op.drop_column('test_sets', 'test_path')
|
||||
op.drop_column('test_sets', 'additional_arguments')
|
||||
op.drop_column('test_sets', 'driver')
|
||||
op.drop_column('test_sets', 'meta')
|
||||
op.drop_column('test_sets', 'cleanup_path')
|
||||
op.alter_column('test_runs', 'status',
|
||||
existing_type=sa.VARCHAR(length=128),
|
||||
nullable=True)
|
||||
op.add_column('test_runs', sa.Column(u'data', sa.TEXT(), nullable=True))
|
||||
op.add_column('test_runs',
|
||||
sa.Column(u'external_id', sa.VARCHAR(length=128),
|
||||
nullable=True))
|
||||
op.add_column('test_runs', sa.Column(u'stats', sa.TEXT(), nullable=True))
|
||||
op.add_column('test_runs',
|
||||
sa.Column(u'type', sa.VARCHAR(length=128), nullable=True))
|
||||
op.drop_column('test_runs', 'cluster_id')
|
||||
op.drop_column('test_runs', 'meta')
|
||||
op.drop_column('test_runs', 'test_set_id')
|
||||
### end Alembic commands ###
|
@ -1,54 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Storage refactor
|
||||
|
||||
Revision ID: 1e2c38f575fb
|
||||
Revises: 1fcb29d29e03
|
||||
Create Date: 2013-07-02 22:43:00.844574
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1e2c38f575fb'
|
||||
down_revision = '1fcb29d29e03'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
'test_sets',
|
||||
sa.Column('id', sa.String(length=128), nullable=False),
|
||||
sa.Column('description', sa.String(length=128), nullable=True),
|
||||
sa.Column('data', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.add_column(u'test_runs', sa.Column('external_id', sa.String(length=128),
|
||||
nullable=True))
|
||||
op.add_column(u'test_runs', sa.Column('stats', sa.Text(), nullable=True))
|
||||
op.add_column(u'tests', sa.Column('test_set_id', sa.String(length=128),
|
||||
nullable=True))
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column(u'tests', 'test_set_id')
|
||||
op.drop_column(u'test_runs', 'stats')
|
||||
op.drop_column(u'test_runs', 'external_id')
|
||||
op.drop_table('test_sets')
|
||||
### end Alembic commands ###
|
@ -1,60 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""initial migration
|
||||
|
||||
Revision ID: 1fcb29d29e03
|
||||
Revises: None
|
||||
Create Date: 2013-06-26 17:40:23.908062
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1fcb29d29e03'
|
||||
down_revision = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
op.create_table(
|
||||
'test_runs',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('type', sa.String(length=128), nullable=True),
|
||||
sa.Column('data', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'tests',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=512), nullable=True),
|
||||
sa.Column('status', sa.String(length=128), nullable=True),
|
||||
sa.Column('taken', sa.Float(), nullable=True),
|
||||
sa.Column('data', sa.Text(), nullable=True),
|
||||
sa.Column('test_run_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['test_run_id'], ['test_runs.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('tests')
|
||||
op.drop_table('test_runs')
|
||||
### end Alembic commands ###
|
@ -1,27 +0,0 @@
|
||||
"""add title column
|
||||
|
||||
Revision ID: 3e45add6471
|
||||
Revises: 1b28f7bc6476
|
||||
Create Date: 2013-08-07 17:01:59.306413
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3e45add6471'
|
||||
down_revision = '1b28f7bc6476'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('tests', sa.Column('title', sa.String(length=512),
|
||||
nullable=True))
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('tests', 'title')
|
||||
### end Alembic commands ###
|
@ -1,44 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Add started_at , ended_at on TestRun model
|
||||
|
||||
Revision ID: 4e9905279776
|
||||
Revises: 12340edd992d
|
||||
Create Date: 2013-07-04 12:10:49.219213
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4e9905279776'
|
||||
down_revision = '12340edd992d'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('test_runs',
|
||||
sa.Column('started_at', sa.DateTime(), nullable=True))
|
||||
op.add_column('test_runs',
|
||||
sa.Column('ended_at', sa.DateTime(), nullable=True))
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('test_runs', 'ended_at')
|
||||
op.drop_column('test_runs', 'started_at')
|
||||
### end Alembic commands ###
|
@ -1,277 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import joinedload, relationship, object_mapper
|
||||
|
||||
from ostf_adapter.storage import fields, engine
|
||||
from ostf_adapter import nose_plugin
|
||||
|
||||
|
||||
BASE = declarative_base()
|
||||
|
||||
|
||||
class TestRun(BASE):
|
||||
|
||||
__tablename__ = 'test_runs'
|
||||
|
||||
STATES = (
|
||||
'running',
|
||||
'finished'
|
||||
)
|
||||
|
||||
id = sa.Column(sa.Integer(), primary_key=True)
|
||||
cluster_id = sa.Column(sa.Integer(), nullable=False)
|
||||
status = sa.Column(sa.Enum(*STATES, name='test_run_states'),
|
||||
nullable=False)
|
||||
meta = sa.Column(fields.JsonField())
|
||||
started_at = sa.Column(sa.DateTime, default=datetime.utcnow)
|
||||
ended_at = sa.Column(sa.DateTime)
|
||||
test_set_id = sa.Column(sa.String(128), sa.ForeignKey('test_sets.id'))
|
||||
|
||||
test_set = relationship('TestSet', backref='test_runs')
|
||||
tests = relationship('Test', backref='test_run', order_by='Test.name')
|
||||
|
||||
def update(self, session, status):
|
||||
self.status = status
|
||||
if status == 'finished':
|
||||
self.ended_at = datetime.utcnow()
|
||||
session.add(self)
|
||||
|
||||
@property
|
||||
def enabled_tests(self):
|
||||
return [test.name for test in self.tests if test.status != 'disabled']
|
||||
|
||||
def is_finished(self):
|
||||
return self.status == 'finished'
|
||||
|
||||
@property
|
||||
def frontend(self):
|
||||
test_run_data = {
|
||||
'id': self.id,
|
||||
'testset': self.test_set_id,
|
||||
'meta': self.meta,
|
||||
'cluster_id': self.cluster_id,
|
||||
'status': self.status,
|
||||
'started_at': self.started_at,
|
||||
'ended_at': self.ended_at,
|
||||
'tests': []
|
||||
}
|
||||
if self.tests:
|
||||
test_run_data['tests'] = [test.frontend for test in self.tests]
|
||||
return test_run_data
|
||||
|
||||
@classmethod
|
||||
def add_test_run(cls, session, test_set, cluster_id, status='running',
|
||||
tests=None):
|
||||
predefined_tests = tests or []
|
||||
tests = session.query(Test).filter_by(
|
||||
test_set_id=test_set, test_run_id=None)
|
||||
test_run = cls(test_set_id=test_set, cluster_id=cluster_id,
|
||||
status=status)
|
||||
session.add(test_run)
|
||||
for test in tests:
|
||||
session.add(test.copy_test(test_run, predefined_tests))
|
||||
return test_run
|
||||
|
||||
@classmethod
|
||||
def get_last_test_run(cls, session, test_set, cluster_id):
|
||||
test_run = session.query(cls). \
|
||||
filter_by(cluster_id=cluster_id, test_set_id=test_set). \
|
||||
order_by(desc(cls.id)).first()
|
||||
return test_run
|
||||
|
||||
@classmethod
|
||||
def get_test_results(cls):
|
||||
session = engine.get_session()
|
||||
test_runs = session.query(cls). \
|
||||
options(joinedload('tests')). \
|
||||
order_by(desc(cls.id))
|
||||
session.commit()
|
||||
session.close()
|
||||
return test_runs
|
||||
|
||||
@classmethod
|
||||
def get_test_run(cls, session, test_run_id, joined=False):
|
||||
if not joined:
|
||||
test_run = session.query(cls). \
|
||||
filter_by(id=test_run_id).first()
|
||||
else:
|
||||
test_run = session.query(cls). \
|
||||
options(joinedload('tests')). \
|
||||
filter_by(id=test_run_id).first()
|
||||
return test_run
|
||||
|
||||
@classmethod
|
||||
def update_test_run(cls, session, test_run_id, status=None):
|
||||
updated_data = {}
|
||||
if status:
|
||||
updated_data['status'] = status
|
||||
if status in ['finished']:
|
||||
updated_data['ended_at'] = datetime.utcnow()
|
||||
session.query(cls). \
|
||||
filter(cls.id == test_run_id). \
|
||||
update(updated_data, synchronize_session=False)
|
||||
|
||||
@classmethod
|
||||
def is_last_running(cls, session, test_set, cluster_id):
|
||||
test_run = cls.get_last_test_run(session, test_set, cluster_id)
|
||||
return not bool(test_run) or test_run.is_finished()
|
||||
|
||||
@classmethod
|
||||
def start(cls, session, test_set, metadata, tests):
|
||||
plugin = nose_plugin.get_plugin(test_set.driver)
|
||||
if cls.is_last_running(session, test_set.id,
|
||||
metadata['cluster_id']):
|
||||
test_run = cls.add_test_run(
|
||||
session, test_set.id,
|
||||
metadata['cluster_id'], tests=tests)
|
||||
plugin.run(test_run, test_set)
|
||||
return test_run.frontend
|
||||
return {}
|
||||
|
||||
def restart(self, session, tests=None):
|
||||
"""Restart test run with
|
||||
if tests given they will be enabled
|
||||
"""
|
||||
if TestRun.is_last_running(session,
|
||||
self.test_set_id,
|
||||
self.cluster_id):
|
||||
plugin = nose_plugin.get_plugin(self.test_set.driver)
|
||||
self.update(session, 'running')
|
||||
if tests:
|
||||
Test.update_test_run_tests(
|
||||
session, self.id, tests)
|
||||
plugin.run(self, self.test_set, tests)
|
||||
return self.frontend
|
||||
return {}
|
||||
|
||||
def stop(self, session):
|
||||
"""Stop test run if running
|
||||
"""
|
||||
plugin = nose_plugin.get_plugin(self.test_set.driver)
|
||||
killed = plugin.kill(
|
||||
self.id, self.cluster_id,
|
||||
cleanup=self.test_set.cleanup_path)
|
||||
if killed:
|
||||
Test.update_running_tests(
|
||||
session, self.id, status='stopped')
|
||||
return self.frontend
|
||||
|
||||
|
||||
|
||||
class TestSet(BASE):
|
||||
|
||||
__tablename__ = 'test_sets'
|
||||
|
||||
id = sa.Column(sa.String(128), primary_key=True)
|
||||
description = sa.Column(sa.String(256))
|
||||
test_path = sa.Column(sa.String(256))
|
||||
driver = sa.Column(sa.String(128))
|
||||
additional_arguments = sa.Column(fields.ListField())
|
||||
cleanup_path = sa.Column(sa.String(128))
|
||||
meta = sa.Column(fields.JsonField())
|
||||
|
||||
tests = relationship('Test',
|
||||
backref='test_set', order_by='Test.name')
|
||||
|
||||
@property
|
||||
def frontend(self):
|
||||
return {'id': self.id, 'name': self.description}
|
||||
|
||||
@classmethod
|
||||
def get_test_set(cls, session, test_set):
|
||||
return session.query(cls).filter_by(id=test_set).first()
|
||||
|
||||
|
||||
class Test(BASE):
|
||||
|
||||
__tablename__ = 'tests'
|
||||
|
||||
STATES = (
|
||||
'wait_running',
|
||||
'running',
|
||||
'failure',
|
||||
'success',
|
||||
'error',
|
||||
'stopped'
|
||||
)
|
||||
|
||||
id = sa.Column(sa.Integer(), primary_key=True)
|
||||
name = sa.Column(sa.String(512))
|
||||
title = sa.Column(sa.String(512))
|
||||
description = sa.Column(sa.Text())
|
||||
duration = sa.Column(sa.String(512))
|
||||
message = sa.Column(sa.Text())
|
||||
traceback = sa.Column(sa.Text())
|
||||
status = sa.Column(sa.Enum(*STATES, name='test_states'))
|
||||
step = sa.Column(sa.Integer())
|
||||
time_taken = sa.Column(sa.Float())
|
||||
meta = sa.Column(fields.JsonField())
|
||||
|
||||
test_set_id = sa.Column(sa.String(128), sa.ForeignKey('test_sets.id'))
|
||||
test_run_id = sa.Column(sa.Integer(), sa.ForeignKey('test_runs.id'))
|
||||
|
||||
@property
|
||||
def frontend(self):
|
||||
return {
|
||||
'id': self.name,
|
||||
'testset': self.test_set_id,
|
||||
'name': self.title,
|
||||
'description': self.description,
|
||||
'duration': self.duration,
|
||||
'message': self.message,
|
||||
'step': self.step,
|
||||
'status': self.status,
|
||||
'taken': self.time_taken
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def add_result(cls, session, test_run_id, test_name, data):
|
||||
session.query(cls).\
|
||||
filter_by(name=test_name, test_run_id=test_run_id).\
|
||||
update(data, synchronize_session=False)
|
||||
|
||||
@classmethod
|
||||
def update_running_tests(cls, session, test_run_id, status='stopped'):
|
||||
session.query(cls). \
|
||||
filter(cls.test_run_id == test_run_id,
|
||||
cls.status.in_(('running', 'wait_running'))). \
|
||||
update({'status': status}, synchronize_session=False)
|
||||
|
||||
@classmethod
|
||||
def update_test_run_tests(cls, session, test_run_id,
|
||||
tests_names, status='wait_running'):
|
||||
session.query(cls). \
|
||||
filter(cls.name.in_(tests_names),
|
||||
cls.test_run_id == test_run_id). \
|
||||
update({'status': status}, synchronize_session=False)
|
||||
|
||||
def copy_test(self, test_run, predefined_tests):
|
||||
new_test = self.__class__()
|
||||
mapper = object_mapper(self)
|
||||
primary_keys = set([col.key for col in mapper.primary_key])
|
||||
for column in mapper.iterate_properties:
|
||||
if column.key not in primary_keys:
|
||||
setattr(new_test, column.key, getattr(self, column.key))
|
||||
new_test.test_run_id = test_run.id
|
||||
if predefined_tests and new_test.name not in predefined_tests:
|
||||
new_test.status = 'disabled'
|
||||
else:
|
||||
new_test.status = 'wait_running'
|
||||
return new_test
|
@ -1,25 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from ostf_adapter.storage import models
|
||||
|
||||
|
||||
def update_all_running_test_runs(session):
|
||||
session.query(models.TestRun). \
|
||||
filter_by(status='running'). \
|
||||
update({'status': 'finished'}, synchronize_session=False)
|
||||
session.query(models.Test). \
|
||||
filter(models.Test.status.in_(('running', 'wait_running'))). \
|
||||
update({'status': 'stopped'}, synchronize_session=False)
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,52 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pecan
|
||||
from ostf_adapter.wsgi import hooks
|
||||
|
||||
|
||||
PECAN_DEFAULT = {
|
||||
'server': {
|
||||
'host': '0.0.0.0',
|
||||
'port': 8989
|
||||
},
|
||||
'app': {
|
||||
'root': 'ostf_adapter.wsgi.root.RootController',
|
||||
'modules': ['ostf_adapter.wsgi']
|
||||
},
|
||||
'nailgun': {
|
||||
'host': '127.0.0.1',
|
||||
'port': 8000
|
||||
},
|
||||
'dbpath': 'postgresql+psycopg2://ostf:ostf@localhost/ostf',
|
||||
'debug': False,
|
||||
'debug_tests': 'functional/dummy_tests'
|
||||
}
|
||||
|
||||
|
||||
def setup_config(pecan_config):
|
||||
pecan_config.update(PECAN_DEFAULT)
|
||||
pecan.conf.update(pecan_config)
|
||||
|
||||
|
||||
def setup_app(config=None):
|
||||
setup_config(config or {})
|
||||
app_hooks = [hooks.SessionHook()]
|
||||
app = pecan.make_app(
|
||||
pecan.conf.app.root,
|
||||
debug=pecan.conf.debug,
|
||||
force_canonical=True,
|
||||
hooks=app_hooks
|
||||
)
|
||||
return app
|
@ -1,135 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from pecan import rest, expose, request
|
||||
from ostf_adapter.storage import models
|
||||
|
||||
from sqlalchemy import desc, func, asc
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseRestController(rest.RestController):
|
||||
def _handle_get(self, method, remainder):
|
||||
if len(remainder):
|
||||
method_name = remainder[0]
|
||||
if method.upper() in self._custom_actions.get(method_name, []):
|
||||
controller = self._find_controller(
|
||||
'get_%s' % method_name,
|
||||
method_name
|
||||
)
|
||||
if controller:
|
||||
return controller, remainder[1:]
|
||||
return super(BaseRestController, self)._handle_get(method, remainder)
|
||||
|
||||
|
||||
class TestsController(BaseRestController):
|
||||
|
||||
@expose('json')
|
||||
def get_one(self, test_name):
|
||||
raise NotImplementedError()
|
||||
|
||||
@expose('json')
|
||||
def get_all(self):
|
||||
with request.session.begin(subtransactions=True):
|
||||
return [item.frontend for item
|
||||
in request.session.query(models.Test).all()]
|
||||
|
||||
|
||||
class TestsetsController(BaseRestController):
|
||||
|
||||
@expose('json')
|
||||
def get_one(self, test_set):
|
||||
with request.session.begin(subtransactions=True):
|
||||
test_set = request.session.query(models.TestSet)\
|
||||
.filter_by(id=test_set).first()
|
||||
if test_set and isinstance(test_set, models.TestSet):
|
||||
return test_set.frontend
|
||||
return {}
|
||||
|
||||
@expose('json')
|
||||
def get_all(self):
|
||||
with request.session.begin(subtransactions=True):
|
||||
return [item.frontend for item
|
||||
in request.session.query(models.TestSet).all()]
|
||||
|
||||
|
||||
class TestrunsController(BaseRestController):
|
||||
|
||||
_custom_actions = {
|
||||
'last': ['GET'],
|
||||
}
|
||||
|
||||
@expose('json')
|
||||
def get_all(self):
|
||||
with request.session.begin(subtransactions=True):
|
||||
return [item.frontend for item
|
||||
in request.session.query(models.TestRun).all()]
|
||||
|
||||
@expose('json')
|
||||
def get_one(self, test_run_id):
|
||||
with request.session.begin(subtransactions=True):
|
||||
test_run = request.session.query(models.TestRun)\
|
||||
.filter_by(id=test_run_id).first()
|
||||
if test_run and isinstance(test_run, models.TestRun):
|
||||
return test_run.frontend
|
||||
return {}
|
||||
|
||||
@expose('json')
|
||||
def get_last(self, cluster_id):
|
||||
with request.session.begin(subtransactions=True):
|
||||
test_run_ids = request.session.query(func.max(models.TestRun.id)) \
|
||||
.group_by(models.TestRun.test_set_id).\
|
||||
filter_by(cluster_id=cluster_id)
|
||||
test_runs = request.session.query(models.TestRun). \
|
||||
options(joinedload('tests')). \
|
||||
filter(models.TestRun.id.in_(test_run_ids))
|
||||
return [item.frontend for item in test_runs]
|
||||
|
||||
@expose('json')
|
||||
def post(self):
|
||||
test_runs = json.loads(request.body)
|
||||
res = []
|
||||
with request.session.begin(subtransactions=True):
|
||||
for test_run in test_runs:
|
||||
test_set = test_run['testset']
|
||||
metadata = test_run['metadata']
|
||||
tests = test_run.get('tests', [])
|
||||
|
||||
test_set = models.TestSet.get_test_set(
|
||||
request.session, test_set)
|
||||
test_run = models.TestRun.start(
|
||||
request.session, test_set, metadata, tests)
|
||||
res.append(test_run)
|
||||
return res
|
||||
|
||||
@expose('json')
|
||||
def put(self):
|
||||
test_runs = json.loads(request.body)
|
||||
data = []
|
||||
with request.session.begin(subtransactions=True):
|
||||
for test_run in test_runs:
|
||||
status = test_run.get('status')
|
||||
tests=test_run.get('tests', [])
|
||||
test_run = models.TestRun.get_test_run(request.session,
|
||||
test_run['id'])
|
||||
if status == 'stopped':
|
||||
data.append(test_run.stop(request.session))
|
||||
elif status == 'restarted':
|
||||
data.append(test_run.restart(request.session, tests=tests))
|
||||
return data
|
@ -1,55 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from pecan import hooks
|
||||
from stevedore import extension
|
||||
|
||||
from ostf_adapter.storage import engine
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExceptionHandlingHook(hooks.PecanHook):
|
||||
def on_error(self, state, e):
|
||||
LOG.exception('Pecan state %s', state)
|
||||
|
||||
|
||||
# class StorageHook(hooks.PecanHook):
|
||||
# def __init__(self):
|
||||
# super(StorageHook, self).__init__()
|
||||
# self.storage = storage.get_storage()
|
||||
#
|
||||
# def before(self, state):
|
||||
# state.request.storage = self.storage
|
||||
|
||||
|
||||
class PluginsHook(hooks.PecanHook):
|
||||
PLUGINS_NAMESPACE = 'plugins'
|
||||
|
||||
def __init__(self):
|
||||
super(PluginsHook, self).__init__()
|
||||
self.plugin_manager = extension.ExtensionManager(
|
||||
self.PLUGINS_NAMESPACE, invoke_on_load=True)
|
||||
|
||||
def before(self, state):
|
||||
state.request.plugin_manager = self.plugin_manager
|
||||
|
||||
|
||||
class SessionHook(hooks.PecanHook):
|
||||
|
||||
def before(self, state):
|
||||
state.request.session = engine.get_session()
|
@ -1,34 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from pecan import expose
|
||||
from ostf_adapter.wsgi import controllers
|
||||
|
||||
|
||||
class V1Controller(object):
|
||||
"""
|
||||
TODO Rewrite it with wsme expose
|
||||
"""
|
||||
|
||||
tests = controllers.TestsController()
|
||||
testsets = controllers.TestsetsController()
|
||||
testruns = controllers.TestrunsController()
|
||||
|
||||
|
||||
class RootController(object):
|
||||
v1 = V1Controller()
|
||||
|
||||
@expose('json', generic=True)
|
||||
def index(self):
|
||||
return {}
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,135 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import requests
|
||||
from json import dumps
|
||||
import time
|
||||
|
||||
|
||||
class TestingAdapterClient(object):
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
|
||||
def _request(self, method, url, data=None):
|
||||
headers = {'content-type': 'application/json'}
|
||||
|
||||
r = requests.request(method, url, data=data, headers=headers, timeout=30.0)
|
||||
if 2 != r.status_code/100:
|
||||
raise AssertionError('{method} "{url}" responded with '
|
||||
'"{code}" status code'.format(
|
||||
method=method.upper(),
|
||||
url=url, code=r.status_code))
|
||||
return r
|
||||
|
||||
def __getattr__(self, item):
|
||||
getters = ['testsets', 'tests', 'testruns']
|
||||
if item in getters:
|
||||
url = ''.join([self.url, '/', item])
|
||||
return lambda: self._request('GET', url)
|
||||
|
||||
def testruns_last(self, cluster_id):
|
||||
url = ''.join([self.url, '/testruns/last/',
|
||||
str(cluster_id)])
|
||||
return self._request('GET', url)
|
||||
|
||||
def start_testrun(self, testset, cluster_id):
|
||||
return self.start_testrun_tests(testset, [], cluster_id)
|
||||
|
||||
def start_testrun_tests(self, testset, tests, cluster_id):
|
||||
url = ''.join([self.url, '/testruns'])
|
||||
data = [{'testset': testset,
|
||||
'tests': tests,
|
||||
'metadata': {'cluster_id': str(cluster_id)}}]
|
||||
return self._request('POST', url, data=dumps(data))
|
||||
|
||||
def stop_testrun(self, testrun_id):
|
||||
url = ''.join([self.url, '/testruns'])
|
||||
data = [{"id": testrun_id,
|
||||
"status": "stopped"}]
|
||||
return self._request("PUT", url, data=dumps(data))
|
||||
|
||||
def stop_testrun_last(self, testset, cluster_id):
|
||||
latest = self.testruns_last(cluster_id).json()
|
||||
testrun_id = [item['id'] for item in latest
|
||||
if item['testset'] == testset][0]
|
||||
return self.stop_testrun(testrun_id)
|
||||
|
||||
def restart_tests(self, tests, testrun_id):
|
||||
url = ''.join([self.url, '/testruns'])
|
||||
body = [{'id': str(testrun_id),
|
||||
'tests': tests,
|
||||
'status': 'restarted'}]
|
||||
return self._request('PUT', url, data=dumps(body))
|
||||
|
||||
def restart_tests_last(self, testset, tests, cluster_id):
|
||||
latest = self.testruns_last(cluster_id).json()
|
||||
testrun_id = [item['id'] for item in latest
|
||||
if item['testset'] == testset][0]
|
||||
return self.restart_tests(tests, testrun_id)
|
||||
|
||||
def _with_timeout(self, action, testset, cluster_id,
|
||||
timeout, polling=5, polling_hook=None):
|
||||
start_time = time.time()
|
||||
json = action().json()
|
||||
|
||||
if json == [{}]:
|
||||
self.stop_testrun_last(testset, cluster_id)
|
||||
time.sleep(1)
|
||||
action()
|
||||
|
||||
while time.time() - start_time <= timeout:
|
||||
time.sleep(polling)
|
||||
|
||||
current_response = self.testruns_last(cluster_id)
|
||||
if polling_hook:
|
||||
polling_hook(current_response)
|
||||
current_status, current_tests = \
|
||||
[(item['status'], item['tests']) for item
|
||||
in current_response.json() if item['testset'] == testset][0]
|
||||
|
||||
if current_status == 'finished':
|
||||
break
|
||||
else:
|
||||
stopped_response = self.stop_testrun_last(testset, cluster_id)
|
||||
if polling_hook:
|
||||
polling_hook(stopped_response)
|
||||
stopped_response = self.testruns_last(cluster_id)
|
||||
stopped_status = [item['status'] for item in stopped_response.json()
|
||||
if item['testset'] == testset][0]
|
||||
|
||||
msg = '{0} is still in {1} state. Now the state is {2}'.format(
|
||||
testset, current_status, stopped_status)
|
||||
msg_tests = '\n'.join(['{0} -> {1}, {2}'.format(
|
||||
item['id'], item['status'], item['taken'])
|
||||
for item in current_tests])
|
||||
raise AssertionError('\n'.join([msg, msg_tests]))
|
||||
return current_response
|
||||
|
||||
def run_with_timeout(self, testset, tests, cluster_id, timeout, polling=5,
|
||||
polling_hook=None):
|
||||
action = lambda: self.start_testrun_tests(testset, tests, cluster_id)
|
||||
return self._with_timeout(action, testset, cluster_id, timeout,
|
||||
polling, polling_hook)
|
||||
|
||||
def run_testset_with_timeout(self, testset, cluster_id, timeout,
|
||||
polling=5, polling_hook=None):
|
||||
return self.run_with_timeout(testset, [], cluster_id, timeout,
|
||||
polling, polling_hook)
|
||||
|
||||
def restart_with_timeout(self, testset, tests, cluster_id, timeout):
|
||||
action = lambda: self.restart_tests_last(testset, tests, cluster_id)
|
||||
return self._with_timeout(action, testset, cluster_id, timeout)
|
||||
|
||||
|
||||
|
@ -1,166 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Openstack testing framework client
|
||||
|
||||
Usage: ostf.py run <test_set> [-q] [--id=<cluster_id>] [--tests=<tests>] [--url=<url>] [--timeout=<timeout>]
|
||||
ostf.py list [<test_set>]
|
||||
|
||||
-q Show test run result only after finish
|
||||
-h --help Show this screen
|
||||
--tests=<tests> Tests to run
|
||||
--id=<cluster_id> Cluster id to use, default: OSTF_CLUSTER_ID or "1"
|
||||
--url=<url> Ostf url, default: OSTF_URL or http://0.0.0.0:8989/v1
|
||||
--timeout=<timeout> Amount of time after which test_run will be stopped [default: 60]
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from docopt import docopt
|
||||
from clint.textui import puts, colored, columns, indent
|
||||
from blessings import Terminal
|
||||
from requests import get
|
||||
from client import TestingAdapterClient
|
||||
|
||||
|
||||
def get_cluster_id():
|
||||
try:
|
||||
r = get('http://localhost:8000/api/clusters').json()
|
||||
except:
|
||||
return 0
|
||||
return next(item['id'] for item in r)
|
||||
|
||||
|
||||
def main():
|
||||
t = Terminal()
|
||||
args = docopt(__doc__, version='0.1')
|
||||
test_set = args['<test_set>']
|
||||
cluster_id = args['--id'] or os.environ.get('OSTF_CLUSTER_ID') \
|
||||
or get_cluster_id() or '1'
|
||||
tests = args['--tests'] or []
|
||||
timeout = args['--timeout']
|
||||
quite = args['-q']
|
||||
url = args['--url'] or os.environ.get('OSTF_URL') \
|
||||
or 'http://0.0.0.0:8989/v1'
|
||||
|
||||
client = TestingAdapterClient(url)
|
||||
|
||||
def run():
|
||||
col = 60
|
||||
|
||||
statused = dict(
|
||||
running='running',
|
||||
wait_running='wait_running',
|
||||
success=colored.green('success'),
|
||||
finished=colored.blue('finished'),
|
||||
failure=colored.red('failure'),
|
||||
error=colored.red('error'),
|
||||
stopped=colored.red('stopped')
|
||||
)
|
||||
|
||||
tests = [test['id'].split('.')[-1]
|
||||
for test in client.tests().json()
|
||||
if test['testset'] == test_set]
|
||||
|
||||
def print_results(item):
|
||||
if isinstance(item, dict):
|
||||
puts(columns([item['id'].split('.')[-1], col],
|
||||
[statused[item['status']], col]))
|
||||
else:
|
||||
puts(columns([item[0], col],
|
||||
[statused.get(item[1], item[1]), col]))
|
||||
|
||||
def move_up(lines):
|
||||
for _ in range(lines):
|
||||
print t.move_up + t.move_left,
|
||||
|
||||
def polling_hook(response):
|
||||
current_status, current_tests = next(
|
||||
(item['status'], item['tests']) for item in response.json()
|
||||
if item['testset'] == test_set)
|
||||
|
||||
move_up(len(current_tests) + 1)
|
||||
|
||||
for test in current_tests:
|
||||
print_results(test)
|
||||
print_results(['General', current_status])
|
||||
|
||||
def quite_polling_hook(response):
|
||||
if not quite_polling_hook.__dict__.get('published_tests'):
|
||||
quite_polling_hook.__dict__['published_tests'] = []
|
||||
|
||||
current_status, current_tests = next(
|
||||
(item['status'], item['tests']) for item in response.json()
|
||||
if item['testset'] == test_set)
|
||||
|
||||
finished_statuses = ['success', 'failure', 'stopped', 'error']
|
||||
|
||||
finished_tests = [item for item in current_tests
|
||||
if item['status'] in finished_statuses
|
||||
and item
|
||||
not in quite_polling_hook.__dict__['published_tests']]
|
||||
|
||||
for test in finished_tests:
|
||||
print_results(test)
|
||||
quite_polling_hook.__dict__['published_tests'].append(test)
|
||||
|
||||
if current_status == 'finished':
|
||||
print_results(['General', current_status])
|
||||
|
||||
if quite:
|
||||
polling_hook = quite_polling_hook
|
||||
else:
|
||||
for test in tests:
|
||||
print_results([test, 'wait_running'])
|
||||
print_results(['General', 'running'])
|
||||
|
||||
try:
|
||||
r = client.run_testset_with_timeout(test_set, cluster_id,
|
||||
timeout, 2, polling_hook)
|
||||
except AssertionError as e:
|
||||
return 1
|
||||
except KeyboardInterrupt as e:
|
||||
r = client.stop_testrun_last(test_set, cluster_id)
|
||||
print t.move_left + t.move_left,
|
||||
polling_hook(r)
|
||||
|
||||
tests = next(item['tests'] for item in r.json())
|
||||
return any(item['status'] != 'success' for item in tests)
|
||||
|
||||
def list_tests():
|
||||
result = client.tests().json()
|
||||
tests = (test for test in result if test['testset'] == test_set)
|
||||
|
||||
col = 60
|
||||
puts(columns([(colored.red("ID")), col],
|
||||
[(colored.red("NAME")), None]))
|
||||
|
||||
for test in tests:
|
||||
test_id = test['id'].split('.')[-1]
|
||||
puts(columns([test_id, col], [test['name'], None]))
|
||||
|
||||
return 0
|
||||
|
||||
def list_test_sets():
|
||||
result = client.testsets().json()
|
||||
|
||||
col = 60
|
||||
puts(columns([(colored.red("ID")), col],
|
||||
[(colored.red("NAME")), None]))
|
||||
|
||||
for test_set in result:
|
||||
puts(columns([test_set['id'], col], [test_set['name'], None]))
|
||||
return 0
|
||||
|
||||
if args['run']:
|
||||
return run()
|
||||
if test_set:
|
||||
return list_tests()
|
||||
return list_test_sets()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
|
||||
|
||||
|
||||
|
96
setup.py
96
setup.py
@ -1,96 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import multiprocessing
|
||||
import setuptools
|
||||
|
||||
requirements = [
|
||||
'Mako==0.8.1',
|
||||
'MarkupSafe==0.18',
|
||||
'SQLAlchemy==0.8.2',
|
||||
'WebOb==1.2.3',
|
||||
'WebTest==2.0.6',
|
||||
'alembic==0.5.0',
|
||||
'argparse==1.2.1',
|
||||
'beautifulsoup4==4.2.1',
|
||||
'distribute==0.7.3',
|
||||
'gevent==0.13.8',
|
||||
'greenlet==0.4.1',
|
||||
'nose==1.3.0',
|
||||
'pecan==0.3.0',
|
||||
'psycogreen==1.0',
|
||||
'psycopg2==2.5.1',
|
||||
'simplegeneric==0.8.1',
|
||||
'six==1.3.0',
|
||||
'stevedore==0.10',
|
||||
'waitress==0.8.5',
|
||||
'wsgiref==0.1.2',
|
||||
'WSME==0.5b2'
|
||||
]
|
||||
|
||||
test_requires = [
|
||||
'mock==1.0.1',
|
||||
'pep8==1.4.6',
|
||||
'py==1.4.15',
|
||||
'six==1.3.0',
|
||||
'tox==1.5.0',
|
||||
'unittest2',
|
||||
'nose',
|
||||
'requests'
|
||||
]
|
||||
|
||||
setuptools.setup(
|
||||
|
||||
name='testing_adapter',
|
||||
version='0.2',
|
||||
|
||||
description='cloud computing testing',
|
||||
|
||||
zip_safe=False,
|
||||
|
||||
test_suite='tests',
|
||||
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Framework :: Setuptools Plugin',
|
||||
'Environment :: OpenStack',
|
||||
'Intended Audience :: Information Technology',
|
||||
'Intended Audience :: System Administrators',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Topic :: System :: Testing',
|
||||
],
|
||||
|
||||
packages=setuptools.find_packages(
|
||||
exclude=['tests', 'utils', '*_tests']),
|
||||
|
||||
include_package_data=True,
|
||||
|
||||
install_requires=requirements,
|
||||
|
||||
test_requires=test_requires,
|
||||
|
||||
entry_points={
|
||||
'plugins': [
|
||||
'nose = ostf_adapter.nose_plugin.nose_adapter:NoseDriver'
|
||||
],
|
||||
'console_scripts': [
|
||||
'ostf-server = bin.adapter_api:main',
|
||||
'update-commands = test_utils.update_commands:main'
|
||||
]
|
||||
},
|
||||
)
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from requests import get
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
r = get('http://localhost:8000/api/clusters').json()
|
||||
except IOError or ValueError as e:
|
||||
print e.message
|
||||
return 1
|
||||
|
||||
cluster_id = next(item['id'] for item in r)
|
||||
print cluster_id
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
function killapp {
|
||||
netstat -nplt | grep 8989 | grep -o [0-9]*/python | grep -o [0-9]* &> /dev/null || { echo "Not running" && return 1; }
|
||||
while netstat -nplt | grep 8989 &> /dev/null; do
|
||||
declare app_pid=$(netstat -nplt | grep 8989 | grep -o [0-9]*/python | grep -o [0-9]*)
|
||||
echo "Ostf-adapter pid is: $app_pid"
|
||||
kill -9 $app_pid; done
|
||||
}
|
||||
|
||||
function stopapp {
|
||||
if netstat -nplt | grep 8989 &> /dev/null; then
|
||||
supervisorctl stop ostf && killapp
|
||||
else
|
||||
echo "OSTF-server is not running"
|
||||
fi
|
||||
}
|
||||
|
||||
function startapp {
|
||||
if netstat -nplt | grep 8989 | grep -o [0-9]*/python | grep -o [0-9]* &> /dev/null; then
|
||||
echo "Server is already running" && return 1; fi
|
||||
|
||||
supervisorctl start ostf
|
||||
touch testing.log
|
||||
sleep 5
|
||||
count=1
|
||||
while ! tail -1 testing.log | grep "serving on" &> /dev/null || ((count != 20)) ; do sleep 3; ((count+=1)) ; done
|
||||
nc -xvw 2 0.0.0.0 8989 &> /dev/null && echo "Working like a charm" || echo "Not working"
|
||||
}
|
||||
|
||||
function update_tests {
|
||||
if [[ -z $1 && -z $OSTF_TESTS_BRANCH ]] ; then echo "Please specify a branch"; return 1; fi
|
||||
if [[ ! -z $1 ]] ; then
|
||||
git ls-remote --heads git@github.com:Mirantis/fuel-ostf-tests.git $1 | grep $1 &> /dev/null || { echo "No branch" && return 1; }
|
||||
export OSTF_TESTS_BRANCH=$1
|
||||
fi
|
||||
! pip freeze | grep ostf-tests &> /dev/null || pip uninstall -y ostf-tests
|
||||
pip install -e git+ssh://git@github.com/Mirantis/fuel-ostf-tests.git@"$OSTF_TESTS_BRANCH"#egg=ostf-tests
|
||||
}
|
||||
|
||||
function update_adapter {
|
||||
if [[ -z $1 && -z $OSTF_ADAPTER_BRANCH ]] ; then echo "Please specify a branch"; return 1; fi
|
||||
if [[ ! -z $1 ]] ; then
|
||||
git ls-remote --heads git@github.com:Mirantis/fuel-ostf-plugin.git $1 | grep $1 &> /dev/null || { echo "No branch" && return 1; }
|
||||
export OSTF_ADAPTER_BRANCH=$1
|
||||
fi
|
||||
! pip freeze | grep testing_adapter &> /dev/null || pip uninstall -y testing_adapter
|
||||
pip install -e git+ssh://git@github.com/Mirantis/fuel-ostf-plugin.git@"$OSTF_ADAPTER_BRANCH"#egg=ostf-plugin
|
||||
}
|
||||
|
||||
function migrate_db {
|
||||
service postgresql restart
|
||||
while ! service postgresql status | grep running &> /dev/null; do sleep 1; done
|
||||
sleep 30
|
||||
export PGPASSWORD='ostf'
|
||||
psql -U postgres -h localhost -c "drop database if exists testing_adapter"
|
||||
psql -U postgres -h localhost -c "drop user adapter"
|
||||
psql -U postgres -h localhost -c "create role adapter with nosuperuser createdb password 'demo' login"
|
||||
psql -U postgres -h localhost -c "create database testing_adapter"
|
||||
|
||||
ostf-server --after-initialization-environment-hook --dbpath=postgresql+psycopg2://adapter:demo@localhost/testing_adapter
|
||||
}
|
||||
|
||||
function run_functional_tests {
|
||||
[[ ! -z $WORKSPACE ]] || export WORKSPACE=$(pwd)
|
||||
nosetests -q functional/tests.py:AdapterTests --with-xunit --xunit-file=$WORKSPACE/reports/functional.xml
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export PGPASSWORD='ostf'
|
||||
psql -U postgres -h localhost -c "drop user adapter"
|
||||
psql -U postgres -h localhost -c "create role adapter with nosuperuser createdb password 'demo' login"
|
||||
psql -U postgres -h localhost -c "drop database if exists testing_adapter"
|
||||
psql -U postgres -h localhost -c "create database testing_adapter"
|
@ -1,236 +0,0 @@
|
||||
[MASTER]
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Profiled execution.
|
||||
profile=no
|
||||
|
||||
# Add <file or directory> to the black list. It should be a base name, not a
|
||||
# path. You may set this option multiple times.
|
||||
ignore=CVS
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time.
|
||||
disable=F0401,R0201,W0311,C0111
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html
|
||||
output-format=parseable
|
||||
|
||||
# Include message's id in output
|
||||
include-ids=yes
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=yes
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (R0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Add a comment according to your evaluation note. This is used by the global
|
||||
# evaluation report (R0004).
|
||||
comment=no
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=120
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching names used for dummy variables (i.e. not used).
|
||||
dummy-variables-rgx=_|dummy
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamically set).
|
||||
ignored-classes=SQLObject
|
||||
|
||||
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||
# to generated-members.
|
||||
zope=no
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E0201 when accessed.
|
||||
generated-members=REQUEST,acl_users,aq_parent
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Required attributes for module, separated by a comma
|
||||
required-attributes=
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,apply,input
|
||||
|
||||
# Regular expression which should only match correct module names
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression which should only match correct module level names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Regular expression which should only match correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression which should only match correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct instance attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct argument names
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct list comprehension /
|
||||
# generator expression variable names
|
||||
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=app,uwsgi,e,i,j,k,ex,Run,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# Regular expression which should only match functions or classes name which do
|
||||
# not require a docstring
|
||||
no-docstring-rgx=__.*__|[Tt]est.*
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=20
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=10
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branchs=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=10
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=0
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of interface methods to ignore, separated by a comma. This is used for
|
||||
# instance to not check methods defines in Zope's Interface base class.
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
@ -1,37 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__author__ = 'ekonstantinov'
|
||||
from json import loads, dumps
|
||||
|
||||
|
||||
def main():
|
||||
with open('ostf_adapter/commands.json', 'rw+') as commands:
|
||||
data = loads(commands.read())
|
||||
for item in data:
|
||||
if 'argv' in data[item] and item in ['fuel_sanity', 'fuel_smoke']:
|
||||
if "--with-xunit" not in data[item]['argv']:
|
||||
data[item]['argv'].extend(["--with-xunit", '--xunit-file={0}.xml'.format(item)])
|
||||
elif item in ['fuel_sanity', 'fuel_smoke']:
|
||||
data[item]['argv'] = ["--with-xunit", ]
|
||||
test_apps = {"plugin_general": {"test_path": "tests/functional/dummy_tests/general_test.py", "driver": "nose"},
|
||||
"plugin_stopped": {"test_path": "tests/functional/dummy_tests/stopped_test.py", "driver": "nose"}}
|
||||
if 'plugin_general' not in data or 'plugin_stopped' not in data:
|
||||
data.update(test_apps)
|
||||
commands.seek(0)
|
||||
commands.write(dumps(data))
|
||||
commands.truncate()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,48 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import unittest2
|
||||
from ostf_adapter.nose_plugin import nose_discovery
|
||||
from mock import patch
|
||||
from ostf_adapter.storage import models
|
||||
|
||||
|
||||
stopped__profile__ = {
|
||||
"id": "stopped_test",
|
||||
"driver": "nose",
|
||||
"test_path": "functional/dummy_tests/stopped_test.py",
|
||||
"description": "Long running 25 secs fake tests"
|
||||
}
|
||||
general__profile__ = {
|
||||
"id": "general_test",
|
||||
"driver": "nose",
|
||||
"test_path": "functional/dummy_tests/general_test.py",
|
||||
"description": "General fake tests"
|
||||
}
|
||||
|
||||
|
||||
@patch('ostf_adapter.nose_plugin.nose_discovery.engine')
|
||||
class TestNoseDiscovery(unittest2.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.fixtures = [models.TestSet(**stopped__profile__),
|
||||
models.TestSet(**general__profile__)]
|
||||
self.fixtures_iter = iter(self.fixtures)
|
||||
|
||||
def test_discovery(self, engine):
|
||||
engine.get_session().merge.return_value = \
|
||||
lambda *args, **kwargs: self.fixtures_iter.next()
|
||||
nose_discovery.discovery(path='functional/dummy_tests')
|
||||
self.assertEqual(engine.get_session().merge.call_count, 2)
|
||||
|
@ -1,13 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
@ -1,21 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
class TestSqlStorage(unittest2.TestCase):
|
||||
|
||||
def test_add_test_run(self):
|
||||
pass
|
@ -1,132 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from ostf_adapter.wsgi import controllers
|
||||
from ostf_adapter.storage import models
|
||||
import unittest2
|
||||
from mock import patch, MagicMock
|
||||
import json
|
||||
|
||||
|
||||
@patch('ostf_adapter.wsgi.controllers.request')
|
||||
class TestTestsController(unittest2.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.fixtures = [models.Test(), models.Test()]
|
||||
self.controller = controllers.TestsController()
|
||||
|
||||
def test_get_all(self, request):
|
||||
request.session.query().all.return_value =\
|
||||
self.fixtures
|
||||
res = self.controller.get_all()
|
||||
self.assertEqual(res, [f.frontend for f in self.fixtures])
|
||||
|
||||
def test_get_one(self, request):
|
||||
pass
|
||||
|
||||
|
||||
@patch('ostf_adapter.wsgi.controllers.request')
|
||||
class TestTestSetsController(unittest2.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.fixtures = [models.TestSet(), models.TestSet()]
|
||||
self.controller = controllers.TestsetsController()
|
||||
|
||||
def test_get_all(self, request):
|
||||
request.session.query().all.return_value =\
|
||||
self.fixtures
|
||||
res = self.controller.get_all()
|
||||
self.assertEqual(res, [f.frontend for f in self.fixtures])
|
||||
|
||||
def test_get_one(self, request):
|
||||
pass
|
||||
|
||||
@patch('ostf_adapter.wsgi.controllers.request')
|
||||
class TestTestRunsController(unittest2.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.fixtures = [models.TestRun(status='finished'),
|
||||
models.TestRun(status='running')]
|
||||
self.fixtures[0].test_set = models.TestSet(driver='nose')
|
||||
self.storage = MagicMock()
|
||||
self.plugin = MagicMock()
|
||||
self.session = MagicMock()
|
||||
self.controller = controllers.TestrunsController()
|
||||
|
||||
def test_get_all(self, request):
|
||||
request.session.query().all.return_value =\
|
||||
self.fixtures
|
||||
res = self.controller.get_all()
|
||||
self.assertEqual(res, [f.frontend for f in self.fixtures])
|
||||
|
||||
def test_get_one(self, request):
|
||||
request.session.query().filter_by().first.return_value =\
|
||||
self.fixtures[0]
|
||||
res = self.controller.get_one(1)
|
||||
self.assertEqual(res, self.fixtures[0].frontend)
|
||||
|
||||
@patch('ostf_adapter.wsgi.controllers.models')
|
||||
def test_post(self, models, request):
|
||||
request.storage = self.storage
|
||||
testruns = [
|
||||
{'testset': 'test_simple',
|
||||
'metadata': {'cluster_id': 3}
|
||||
},
|
||||
{'testset': 'test_simple',
|
||||
'metadata': {'cluster_id': 4}
|
||||
}]
|
||||
request.body = json.dumps(testruns)
|
||||
fixtures_iterable = (f.frontend for f in self.fixtures)
|
||||
|
||||
models.TestRun.start.side_effect = \
|
||||
lambda *args, **kwargs: fixtures_iterable.next()
|
||||
res = self.controller.post()
|
||||
self.assertEqual(res, [f.frontend for f in self.fixtures])
|
||||
|
||||
@patch('ostf_adapter.wsgi.controllers.models')
|
||||
def test_put_stopped(self, models, request):
|
||||
request.storage = self.storage
|
||||
testruns = [
|
||||
{'id': 1,
|
||||
'metadata': {'cluster_id': 4},
|
||||
'status': 'stopped'
|
||||
}]
|
||||
request.body = json.dumps(testruns)
|
||||
|
||||
models.TestRun.get_test_run().stop.side_effect = \
|
||||
lambda *args, **kwargs: self.fixtures[0].frontend
|
||||
res = self.controller.put()
|
||||
self.assertEqual(res, [self.fixtures[0].frontend])
|
||||
|
||||
@patch('ostf_adapter.wsgi.controllers.models')
|
||||
def test_put_restarted(self, models, request):
|
||||
request.storage = self.storage
|
||||
testruns = [
|
||||
{'id': 1,
|
||||
'metadata': {'cluster_id': 4},
|
||||
'status': 'restarted'
|
||||
}]
|
||||
request.body = json.dumps(testruns)
|
||||
|
||||
models.TestRun.get_test_run().restart.side_effect = \
|
||||
lambda *args, **kwargs: self.fixtures[0].frontend
|
||||
res = self.controller.put()
|
||||
self.assertEqual(res, [self.fixtures[0].frontend])
|
||||
|
||||
def test_get_last(self, request):
|
||||
cluster_id = 1
|
||||
request.session.query().group_by().filter_by.return_value = [10, 11]
|
||||
request.session.query().options().filter.return_value = self.fixtures
|
||||
res = self.controller.get_last(cluster_id)
|
||||
self.assertEqual(res, [f.frontend for f in self.fixtures])
|
@ -1,76 +0,0 @@
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import unittest2
|
||||
from mock import patch, MagicMock
|
||||
from webtest import TestApp
|
||||
from ostf_adapter.wsgi import app
|
||||
import json
|
||||
|
||||
|
||||
@patch('ostf_adapter.wsgi.controllers.request')
|
||||
class WsgiInterfaceTests(unittest2.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = TestApp(app.setup_app())
|
||||
|
||||
def test_get_all_tests(self, request):
|
||||
self.app.get('/v1/tests')
|
||||
|
||||
def test_get_one_test(self, request):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.app.get,
|
||||
'/v1/tests/1')
|
||||
|
||||
def test_get_all_testsets(self, request):
|
||||
self.app.get('/v1/testsets')
|
||||
|
||||
def test_get_one_testset(self, request):
|
||||
self.app.get('/v1/testsets/plugin_test')
|
||||
|
||||
def test_get_one_testruns(self, request):
|
||||
self.app.get('/v1/testruns/1')
|
||||
|
||||
def test_get_all_testruns(self, request):
|
||||
self.app.get('/v1/testruns')
|
||||
|
||||
@patch('ostf_adapter.wsgi.controllers.models')
|
||||
def test_post_testruns(self, models, request):
|
||||
testruns = [
|
||||
{'testset': 'test_simple',
|
||||
'metadata': {'cluster_id': 3}
|
||||
},
|
||||
{'testset': 'test_simple',
|
||||
'metadata': {'cluster_id': 4}
|
||||
}]
|
||||
request.body = json.dumps(testruns)
|
||||
models.TestRun.start.return_value = {}
|
||||
self.app.post_json('/v1/testruns', testruns)
|
||||
|
||||
def test_put_testruns(self, request):
|
||||
testruns = [
|
||||
{'id': 2,
|
||||
'metadata': {'cluster_id': 3},
|
||||
'status': 'non_exist'
|
||||
},
|
||||
{'id': 1,
|
||||
'metadata': {'cluster_id': 4},
|
||||
'status': 'non_exist'
|
||||
}]
|
||||
request.body = json.dumps(testruns)
|
||||
request.storage.get_test_run.return_value = MagicMock(frontend={})
|
||||
self.app.put_json('/v1/testruns', testruns)
|
||||
|
||||
def test_get_last_testruns(self, request):
|
||||
self.app.get('/v1/testruns/last/101')
|
@ -1 +0,0 @@
|
||||
git+ssh://git@github.com/Mirantis/fuel-ostf-tests.git@stable#egg=ostf-tests==0.1
|
@ -1,20 +0,0 @@
|
||||
'Mako==0.8.1'
|
||||
'MarkupSafe==0.18'
|
||||
'SQLAlchemy==0.8.2'
|
||||
'WebOb==1.2.3'
|
||||
'WebTest==2.0.6'
|
||||
'alembic==0.5.0'
|
||||
'argparse==1.2.1'
|
||||
'beautifulsoup4==4.2.1'
|
||||
'distribute==0.7.3'
|
||||
'gevent==0.13.8'
|
||||
'greenlet==0.4.1'
|
||||
'nose==1.3.0'
|
||||
'pecan==0.3.0'
|
||||
'psycogreen==1.0'
|
||||
'psycopg2==2.5.1'
|
||||
'simplegeneric==0.8.1'
|
||||
'six==1.3.0'
|
||||
'stevedore==0.10'
|
||||
'waitress==0.8.5'
|
||||
'wsgiref==0.1.2'
|
@ -1,9 +0,0 @@
|
||||
WebTest==2.0.6
|
||||
mock==1.0.1
|
||||
pep8==1.4.6
|
||||
py==1.4.15
|
||||
six==1.3.0
|
||||
tox==1.5.0
|
||||
unittest2==0.5.1
|
||||
coverage==3.6
|
||||
requests==1.2.3
|
21
tox.ini
21
tox.ini
@ -1,21 +0,0 @@
|
||||
# Tox (http://tox.testrun.org/) is a tool for running tests
|
||||
# in multiple virtualenvs. This configuration file will run the
|
||||
# test suite on all supported python versions. To use it, "pip install tox"
|
||||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py26, py27, pep8, cover
|
||||
|
||||
[testenv]
|
||||
commands = nosetests tests
|
||||
deps = -r{toxinidir}/tools/test-requires
|
||||
|
||||
[testenv:pep8]
|
||||
commands = pep8 --repeat --show-source --exclude=.venv,.tox,dist,doc,*egg,tests,functional,test_utils,ostf_client .
|
||||
|
||||
[testenv:cover]
|
||||
commands = nosetests tests --no-path-adjustment --with-coverage --cover-erase --cover-package=ostf_adapter
|
||||
|
||||
[testenv:funct]
|
||||
deps = requests
|
||||
commands = nosetests functional/tests.py:AdapterTests
|
Loading…
Reference in New Issue
Block a user