Adding in integration tests

blueprint integration-tests

Change-Id: I3f7c8200a03ea60bca23563b49de8fd85126bf8d
This commit is contained in:
John Bresnahan 2013-08-02 12:15:31 -10:00
parent ee9d9d2b3b
commit e77bbe5e97
9 changed files with 457 additions and 51 deletions

View File

@ -73,28 +73,19 @@ class XferController(object):
self.conf = conf self.conf = conf
def _xfer_from_db(self, xfer_id, owner): def _xfer_from_db(self, xfer_id, owner):
try: return self.db_con.lookup_xfer_request_by_id(
return self.db_con.lookup_xfer_request_by_id( xfer_id, owner=owner)
xfer_id, owner=owner)
except exceptions.StaccatoNotFoundInDBException, db_ex:
raise webob.exc.HTTPNotFound(explanation="No such ID %s" % xfer_id,
content_type="text/plain")
def _to_state_machine(self, event, xfer_request, name): def _to_state_machine(self, event, xfer_request, name):
try: self.sm.event_occurred(event,
self.sm.event_occurred(event, xfer_request=xfer_request,
xfer_request=xfer_request, db=self.db_con)
db=self.db_con)
except exceptions.StaccatoInvalidStateTransitionException, ex:
msg = _('You cannot %s a transfer that is in the %s '
'state. %s' % (name, xfer_request.state, ex))
self._log_request(logging.INFO, msg)
raise webob.exc.HTTPBadRequest(explanation=msg,
content_type="text/plain")
@utils.StaccatoErrorToHTTP('Create a new transfer', LOG)
def newtransfer(self, request, source_url, destination_url, owner, def newtransfer(self, request, source_url, destination_url, owner,
source_options=None, destination_options=None, source_options=None, destination_options=None,
start_offset=0, end_offset=None): start_offset=0, end_offset=None):
srcurl_parts = urlparse.urlparse(source_url) srcurl_parts = urlparse.urlparse(source_url)
dsturl_parts = urlparse.urlparse(destination_url) dsturl_parts = urlparse.urlparse(destination_url)
@ -129,40 +120,29 @@ class XferController(object):
dest_opts=dstopts) dest_opts=dstopts)
return xfer return xfer
@utils.StaccatoErrorToHTTP('Check the status', LOG)
def status(self, request, xfer_id, owner): def status(self, request, xfer_id, owner):
xfer = self._xfer_from_db(xfer_id, owner) xfer = self._xfer_from_db(xfer_id, owner)
return xfer return xfer
def list(self, request, owner): @utils.StaccatoErrorToHTTP('List transfers', LOG)
return self.db_con.lookup_xfer_request_all(owner=owner) def list(self, request, owner, limit=None):
return self.db_con.lookup_xfer_request_all(owner=owner, limit=limit)
def _xfer_to_dict(self, x):
d = {}
d['id'] = x.id
d['srcurl'] = x.srcurl
d['dsturl'] = x.dsturl
d['state'] = x.state
d['progress'] = x.next_ndx
return d
@utils.StaccatoErrorToHTTP('Delete a transfer', LOG)
def delete(self, request, xfer_id, owner): def delete(self, request, xfer_id, owner):
xfer_request = self._xfer_from_db(xfer_id, owner) xfer_request = self._xfer_from_db(xfer_id, owner)
self._to_state_machine(Events.EVENT_DELETE, self._to_state_machine(Events.EVENT_DELETE,
xfer_request, xfer_request,
'delete') 'delete')
@utils.StaccatoErrorToHTTP('Cancel a transfer', LOG)
def xferaction(self, request, xfer_id, owner, xferaction, **kwvals): def xferaction(self, request, xfer_id, owner, xferaction, **kwvals):
xfer_request = self._xfer_from_db(xfer_id, owner) xfer_request = self._xfer_from_db(xfer_id, owner)
self._to_state_machine(Events.EVENT_CANCEL, self._to_state_machine(Events.EVENT_CANCEL,
xfer_request, xfer_request,
'cancel') 'cancel')
def _log_request(self, level, msg, ex=None):
# reformat the exception with context, user info, etc
if ex:
self.log.exception(msg)
self.log.log(level, msg)
class XferHeaderDeserializer(os_wsgi.RequestHeadersDeserializer): class XferHeaderDeserializer(os_wsgi.RequestHeadersDeserializer):
def default(self, request): def default(self, request):
@ -201,14 +181,6 @@ class XferDeserializer(os_wsgi.JSONDeserializer):
request = self._validate(self._from_json(body), _required, _optional) request = self._validate(self._from_json(body), _required, _optional)
return request return request
def status(self, body):
request = self._validate(self._from_json(body), [], [])
return request
def delete(self, body):
request = self._validate(self._from_json(body), [], [])
return request
def cancel(self, body): def cancel(self, body):
_required = ['xferaction'] _required = ['xferaction']
_optional = ['async'] _optional = ['async']

View File

@ -33,5 +33,3 @@ def main():
server.wait() server.wait()
except RuntimeError as e: except RuntimeError as e:
fail(1, e) fail(1, e)
main()

View File

@ -45,4 +45,7 @@ class StaccatoDatabaseException(StaccatoBaseException):
class StaccatoNotFoundInDBException(StaccatoDataBaseException): class StaccatoNotFoundInDBException(StaccatoDataBaseException):
pass
def __init__(self, ex, unfound_item):
super(StaccatoNotFoundInDBException, self).__init__(self, ex)
self.unfound_item = unfound_item

View File

@ -2,6 +2,8 @@ import logging
import re import re
from paste import deploy from paste import deploy
import webob
import webob.exc
from staccato.common import exceptions from staccato.common import exceptions
from staccato.openstack.common import importutils from staccato.openstack.common import importutils
@ -16,6 +18,45 @@ def not_implemented_decorator(func):
return call return call
class StaccatoErrorToHTTP(object):
def __init__(self, operation, log):
self.operation = operation
self.log = log
def __call__(self, func):
def inner(*args, **kwargs):
try:
return func(*args, **kwargs)
except exceptions.StaccatoNotFoundInDBException as ex:
msg = _("Failed to %s. %s not found.") % (self.operation,
ex.unfound_item)
self.log.error(msg)
raise webob.exc.HTTPNotFound(explanation=msg,
content_type="text/plain")
except exceptions.StaccatoInvalidStateTransitionException, ex:
msg = _('Failed to %s. You cannot %s a transfer that is in '
'the %s state. %s' % (self.operation,
ex.attempted_event,
ex.current_state,
ex))
self.log.error(msg)
raise webob.exc.HTTPBadRequest(explanation=msg,
content_type="text/plain")
except exceptions.StaccatoParameterError as ex:
msg = _('Failed to %s. %s' % (self.operation, ex))
self.log.error(msg)
raise webob.exc.HTTPBadRequest(msg)
except Exception as ex:
msg = _('Failed to %s. %s' % (self.operation, ex))
self.log.error(msg)
raise webob.exc.HTTPBadRequest(msg)
return inner
def load_paste_app(app_name, conf_file, conf): def load_paste_app(app_name, conf_file, conf):
try: try:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -85,11 +85,11 @@ class StaccatoDB(object):
xfer_request = query.one() xfer_request = query.one()
return xfer_request return xfer_request
except orm_exc.NoResultFound, nf_ex: except orm_exc.NoResultFound, nf_ex:
raise exceptions.StaccatoNotFoundInDBException(nf_ex) raise exceptions.StaccatoNotFoundInDBException(nf_ex, xfer_id)
except Exception, ex: except Exception, ex:
raise exceptions.StaccatoDataBaseException(ex) raise exceptions.StaccatoDataBaseException(ex)
def lookup_xfer_request_all(self, owner=None, session=None): def lookup_xfer_request_all(self, owner=None, session=None, limit=None):
try: try:
if session is None: if session is None:
session = self.get_sessions() session = self.get_sessions()
@ -98,10 +98,12 @@ class StaccatoDB(object):
query = session.query(models.XferRequest) query = session.query(models.XferRequest)
if owner is not None: if owner is not None:
query = query.filter(models.XferRequest.owner == owner) query = query.filter(models.XferRequest.owner == owner)
if limit is not None:
query = query.limit(limit)
xfer_requests = query.all() xfer_requests = query.all()
return xfer_requests return xfer_requests
except orm_exc.NoResultFound, nf_ex: except orm_exc.NoResultFound, nf_ex:
raise exceptions.StaccatoNotFoundInDBException(nf_ex) raise exceptions.StaccatoNotFoundInDBException(nf_ex, owner)
except Exception, ex: except Exception, ex:
raise exceptions.StaccatoDataBaseException(ex) raise exceptions.StaccatoDataBaseException(ex)
@ -182,7 +184,7 @@ def _get_db_object(CONF):
engine = sqlalchemy.create_engine(CONF.sql_connection, **engine_args) engine = sqlalchemy.create_engine(CONF.sql_connection, **engine_args)
engine.connect = wrap_db_error(engine.connect, CONF) engine.connect = wrap_db_error(engine.connect, CONF)
engine.connect() engine.connect()
except Exception, err: except Exception as err:
msg = _("Error configuring registry database with supplied " msg = _("Error configuring registry database with supplied "
"sql_connection '%s'. " "sql_connection '%s'. "
"Got error:\n%s") % (CONF.sql_connection, err) "Got error:\n%s") % (CONF.sql_connection, err)

View File

@ -0,0 +1 @@
__author__ = 'jbresnah'

View File

@ -0,0 +1,104 @@
# 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.path
import json
import staccato.common.config as config
import staccato.common.utils as staccato_utils
import staccato.openstack.common.pastedeploy as os_pastedeploy
import staccato.tests.utils as test_utils
TESTING_API_PASTE_CONF = """
[pipeline:staccato-api]
pipeline = unauthenticated-context rootapp
# Use this pipeline for keystone auth
[pipeline:staccato-api-keystone]
pipeline = authtoken context rootapp
[app:rootapp]
use = egg:Paste#urlmap
/v1: apiv1app
/: apiversions
[app:apiversions]
paste.app_factory = staccato.openstack.common.pastedeploy:app_factory
openstack.app_factory = staccato.api.versions:VersionApp
[app:apiv1app]
paste.app_factory = staccato.openstack.common.pastedeploy:app_factory
openstack.app_factory = staccato.api.v1.xfer:API
[filter:unauthenticated-context]
paste.filter_factory = staccato.openstack.common.pastedeploy:filter_factory
openstack.filter_factory = staccato.api.v1.xfer:UnauthTestMiddleware
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
delay_auth_decision = true
[filter:context]
paste.filter_factory = staccato.openstack.common.pastedeploy:filter_factory
openstack.filter_factory = staccato.api.v1.xfer:AuthContextMiddleware
"""
class ApiTestBase(test_utils.TempFileCleanupBaseTest):
def setUp(self):
super(ApiTestBase, self).setUp()
self.test_dir = self.get_tempdir()
self.sql_connection = 'sqlite://'
self.conf = config.get_config_object(args=[])
self.config(sql_connection=self.sql_connection)
self.write_protocol_module_file()
self.config(db_auto_create=True)
self.needs_database = True
def get_http_client(self):
staccato_api = self._load_paste_app(
'staccato-api', TESTING_API_PASTE_CONF, self.conf)
return test_utils.Httplib2WsgiAdapter(staccato_api)
def _load_paste_app(self, name, paste_conf, conf):
conf_file_path = os.path.join(self.test_dir, '%s-paste.ini' % name)
with open(conf_file_path, 'wb') as conf_file:
conf_file.write(paste_conf)
conf_file.flush()
return os_pastedeploy.paste_deploy_app(conf_file_path,
name,
conf)
def tearDown(self):
super(ApiTestBase, self).tearDown()
def config(self, **kw):
group = kw.pop('group', None)
for k, v in kw.iteritems():
self.conf.set_override(k, v, group)
def write_protocol_module_file(self, protocols=None):
if protocols is None:
protocols = {
"file": [{"module": "staccato.protocols.file.FileProtocol"}],
"http": [{"module": "staccato.protocols.http.HttpProtocol"}]
}
temp_file = self.get_tempfile()
with open(temp_file, 'w') as fp:
json.dump(protocols, fp)
self.config(protocol_policy=temp_file)
return temp_file

View File

@ -0,0 +1,234 @@
import json
import staccato.tests.integration.base as base
class TestApiNoSchedulerBasicFunctions(base.ApiTestBase):
def _list_transfers(self, http_client):
path = "/v1/transfers"
response, content = http_client.request(path, 'GET')
self.assertEqual(response.status, 200)
data = json.loads(content)
return data
def _cancel_transfer(self, http_client, id):
data_json = {'xferaction': 'cancel'}
data = json.dumps(data_json)
headers = {'content-type': 'application/json'}
path = "/v1/transfers/%s/action" % id
return http_client.request(path, 'POST', headers=headers, body=data)
def _delete_transfer(self, http_client, id):
path = "/v1/transfers/%s" % id
return http_client.request(path, 'DELETE')
def _status_transfer(self, http_client, id):
path = "/v1/transfers/%s" % id
return http_client.request(path, 'GET')
def _create_xfer(self, http_client, src='file:///etc/group',
dst='file:///dev/null'):
path = "/v1/transfers"
data_json = {'source_url': src,
'destination_url': dst}
data = json.dumps(data_json)
headers = {'content-type': 'application/json'}
response, content = http_client.request(path, 'POST', body=data,
headers=headers)
return response, content
def test_get_simple_empty_list(self):
http_client = self.get_http_client()
data = self._list_transfers(http_client)
self.assertEqual([], data)
def test_simple_create_transfer(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
def test_simple_create_transfer_list(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
data = self._list_transfers(http_client)
self.assertEqual(len(data), 1)
def test_simple_create_transfer_list_delete(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
data = self._list_transfers(http_client)
self.assertEqual(len(data), 1)
response, content = self._delete_transfer(http_client, data[0]['id'])
self.assertEqual(response.status, 200)
data = self._list_transfers(http_client)
self.assertEqual(data, [])
def test_simple_create_transfer_status(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
data = json.loads(content)
response, content = self._status_transfer(http_client, data['id'])
self.assertEqual(response.status, 200)
data_status = json.loads(content)
self.assertEquals(data, data_status)
def test_delete_unknown(self):
http_client = self.get_http_client()
response, content = self._delete_transfer(http_client, 'notreal')
self.assertEqual(response.status, 404)
def test_delete_twice(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
data = json.loads(content)
response, content = self._delete_transfer(http_client, data['id'])
self.assertEqual(response.status, 200)
response, content = self._delete_transfer(http_client, data['id'])
self.assertEqual(response.status, 404)
def test_status_unknown(self):
http_client = self.get_http_client()
response, content = self._delete_transfer(http_client, 'notreal')
self.assertEqual(response.status, 404)
def test_status_after_delete(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
data = json.loads(content)
response, content = self._delete_transfer(http_client, data['id'])
self.assertEqual(response.status, 200)
response, content = self._status_transfer(http_client, data['id'])
self.assertEqual(response.status, 404)
def test_create_state(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
data = json.loads(content)
self.assertEqual(data['state'], 'STATE_NEW')
def test_create_cancel(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
data = json.loads(content)
response, content = self._cancel_transfer(http_client, data['id'])
self.assertEqual(response.status, 200)
response, content = self._status_transfer(http_client, data['id'])
self.assertEqual(response.status, 200)
data = json.loads(content)
self.assertEqual(data['state'], 'STATE_CANCELED')
def test_create_cancel_delete(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
data = json.loads(content)
response, content = self._cancel_transfer(http_client, data['id'])
self.assertEqual(response.status, 200)
response, content = self._status_transfer(http_client, data['id'])
self.assertEqual(response.status, 200)
response, content = self._delete_transfer(http_client, data['id'])
self.assertEqual(response.status, 200)
def test_cancel_unknown(self):
http_client = self.get_http_client()
response, content = self._cancel_transfer(http_client, 'notreal')
self.assertEqual(response.status, 404)
def test_simple_create_bad_source(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client, src="bad_form")
self.assertEqual(response.status, 400)
def test_simple_create_bad_dest(self):
http_client = self.get_http_client()
response, content = self._create_xfer(http_client, dst="bad_form")
self.assertEqual(response.status, 400)
def test_bad_update(self):
http_client = self.get_http_client()
data_json = {'notaction': 'cancel'}
data = json.dumps(data_json)
headers = {'content-type': 'application/json'}
path = "/v1/transfers/%s/action" % id
response, content = http_client.request(path, 'POST', headers=headers,
body=data)
self.assertEqual(response.status, 400)
def test_bad_action(self):
http_client = self.get_http_client()
data_json = {'xferaction': 'applesauce'}
data = json.dumps(data_json)
headers = {'content-type': 'application/json'}
path = "/v1/transfers/%s/action" % id
response, content = http_client.request(path, 'POST', headers=headers, body=data)
self.assertEqual(response.status, 400)
def test_create_url_options(self):
path = "/v1/transfers"
http_client = self.get_http_client()
data_json = {'source_url': 'file:///etc/group',
'destination_url': 'file:///dev/null',
'source_options': {'key': 10},
'destination_options': [1, 3, 5]}
data = json.dumps(data_json)
headers = {'content-type': 'application/json'}
response, content = http_client.request(path, 'POST', body=data,
headers=headers)
self.assertEqual(response.status, 200)
data_out = json.loads(content)
self.assertEqual(data_json['source_options'],
data_out['source_options'])
self.assertEqual(data_json['destination_options'],
data_out['destination_options'])
def test_create_missing_url(self):
path = "/v1/transfers"
http_client = self.get_http_client()
data_json = {'source_url': 'file:///etc/group'}
data = json.dumps(data_json)
headers = {'content-type': 'application/json'}
response, content = http_client.request(path, 'POST', body=data,
headers=headers)
self.assertEqual(response.status, 400)
def test_create_uknown_option(self):
path = "/v1/transfers"
http_client = self.get_http_client()
data_json = {'source_url': 'file:///etc/group',
'destination_url': 'file:///dev/zero',
'random': 90}
data = json.dumps(data_json)
headers = {'content-type': 'application/json'}
response, content = http_client.request(path, 'POST', body=data,
headers=headers)
self.assertEqual(response.status, 400)
def test_list_limit(self):
http_client = self.get_http_client()
for i in range(10):
response, content = self._create_xfer(http_client)
self.assertEqual(response.status, 200)
path = "/v1/transfers"
data_json = {'limit': 5}
data = json.dumps(data_json)
headers = {'content-type': 'application/json'}
response, content = http_client.request(path, 'GET', body=data,
headers=headers)
self.assertEqual(response.status, 200)
data = json.loads(content)
self.assertEqual(len(data), 5)

View File

@ -1,8 +1,10 @@
import tempfile import tempfile
import testtools import testtools
import staccato.db as db
import os import os
import json import json
import webob
import staccato.db as db
TEST_CONF = """ TEST_CONF = """
[DEFAULT] [DEFAULT]
@ -17,8 +19,11 @@ FILE_ONLY_PROTOCOL = {
"file": [{"module": "staccato.protocols.file.FileProtocol"}] "file": [{"module": "staccato.protocols.file.FileProtocol"}]
} }
class BaseTestCase(testtools.TestCase):
pass
class TempFileCleanupBaseTest(testtools.TestCase):
class TempFileCleanupBaseTest(BaseTestCase):
def setUp(self): def setUp(self):
super(TempFileCleanupBaseTest, self).setUp() super(TempFileCleanupBaseTest, self).setUp()
@ -54,4 +59,50 @@ class TempFileCleanupBaseTest(testtools.TestCase):
pass pass
def get_tempfile(self): def get_tempfile(self):
return tempfile.mkstemp()[1] fname = tempfile.mkstemp()[1]
self.files_to_delete.append(fname)
return fname
def get_tempdir(self):
return tempfile.mkdtemp()
class Httplib2WsgiAdapter(object):
def __init__(self, app):
self.app = app
def request(self, uri, method="GET", body=None, headers=None):
req = webob.Request.blank(uri, method=method, headers=headers)
req.body = body
resp = req.get_response(self.app)
return Httplib2WebobResponse(resp), resp.body
class Httplib2WebobResponse(object):
def __init__(self, webob_resp):
self.webob_resp = webob_resp
@property
def status(self):
return self.webob_resp.status_code
def __getitem__(self, key):
return self.webob_resp.headers[key]
def get(self, key):
return self.webob_resp.headers[key]
class HttplibWsgiAdapter(object):
def __init__(self, app):
self.app = app
self.req = None
def request(self, method, url, body=None, headers={}):
self.req = webob.Request.blank(url, method=method, headers=headers)
self.req.body = body
def getresponse(self):
response = self.req.get_response(self.app)
return FakeHTTPResponse(response.status_code, response.headers,
response.body)