import atexit import json import os import sys import traceback import webapp2 import webapp2_extras.routes from . import encoder from . import root from . import util from . import config from .request import SciTranRequest from .centralclient import CentralClient from .download import Download from .handlers.collectionshandler import CollectionsHandler from .handlers.confighandler import Config, Version from .handlers.containerhandler import ContainerHandler from .handlers.devicehandler import DeviceHandler from .handlers.grouphandler import GroupHandler from .handlers.listhandler import AnalysesHandler, ListHandler, FileListHandler, NotesListHandler, PermissionsListHandler, TagsListHandler from .handlers.reporthandler import ReportHandler from .handlers.resolvehandler import ResolveHandler from .handlers.schemahandler import SchemaHandler from .handlers.searchhandler import SearchHandler from .handlers.userhandler import UserHandler from .jobs.handlers import JobsHandler, JobHandler, GearsHandler, GearHandler, RulesHandler from .upload import Upload log = config.log try: import uwsgi except ImportError: uwsgi = None routing_regexes = { # Group ID: 2-32 characters of form [0-9a-z.@_-]. Start and ends with alphanum. 'gid': '[0-9a-z][0-9a-z.@_-]{0,30}[0-9a-z]', # Container ID: 24-character hex 'cid': '[0-9a-f]{24}', # Site ID: <= 24-character alphanum 'sid': '[0-9a-z_]{0,24}', # User ID: any length, [0-9a-z.@_-] 'uid': '[0-9a-z.@_-]*', # Container name 'cname': 'projects|sessions|acquisitions|collections', # Tag name 'tag': '[^/]{1,32}', # Filename 'fname': '[^/]+', # Note ID 'nid': '[0-9a-f]{24}', # Schema path 'schema': r'[^/.]{3,60}/[^/.]{3,60}\.json' } def route(path, target, h=None, m=None, name=None): # https://webapp2.readthedocs.io/en/latest/api/webapp2.html#webapp2.Route return webapp2.Route( # re.compile(path) path.format(**routing_regexes), target, handler_method=h, methods=m, name=name ) def prefix(path, routes): # https://webapp2.readthedocs.io/en/latest/api/webapp2_extras/routes.html#webapp2_extras.routes.PathPrefixRoute return webapp2_extras.routes.PathPrefixRoute( path.format(**routing_regexes), routes ) endpoints = [ route('/api', root.Root), prefix('/api', [ # System configuration route('/config', Config, m=['GET']), route('/config.js', Config, h='get_js', m=['GET']), route('/version', Version, m=['GET']), # General-purpose upload & download route('/download', Download, h='download', m=['GET', 'POST']), route('/upload/<strategy:label|uid|uid-match>', Upload, h='upload', m=['POST']), route('/clean-packfiles', Upload, h='clean_packfile_tokens', m=['POST']), route('/engine', Upload, h='engine', m=['POST']), # Top-level endpoints route('/resolve', ResolveHandler, h='resolve', m=['POST']), route('/schemas/<schema:{schema}>', SchemaHandler, m=['GET']), route('/report/<report_type:site|project>', ReportHandler, m=['GET']), # Search route('/search', SearchHandler, h='advanced_search', m=['POST']), route('/search/files', SearchHandler, h='get_datatree', m=['GET']), route('/search/<cont_name:{cname}>', SearchHandler, m=['GET']), # Users route( '/users', UserHandler, 'get_all', m=['GET']), route( '/users', UserHandler, m=['POST']), prefix('/users', [ route('/self', UserHandler, h='self', m=['GET']), route('/self/avatar', UserHandler, h='self_avatar', m=['GET']), route('/self/key', UserHandler, h='generate_api_key',m=['POST']), route('/<_id:{uid}>', UserHandler), route('/<uid:{uid}>/groups', GroupHandler, h='get_all', m=['GET']), route('/<uid:{uid}>/avatar', UserHandler, h='avatar', m=['GET']), route('/<uid:{uid}>/<cont_name:{cname}>', ContainerHandler, h='get_all_for_user', m=['GET']), ]), # Jobs & gears route( '/jobs', JobsHandler), prefix('/jobs', [ route('/next', JobsHandler, h='next', m=['GET']), route('/stats', JobsHandler, h='stats', m=['GET']), route('/reap', JobsHandler, h='reap_stale', m=['POST']), route('/add', JobsHandler, h='add', m=['POST']), route('/<:[^/]+>', JobHandler), route('/<:[^/]+>/config.json', JobHandler, h='get_config'), route('/<:[^/]+>/retry', JobHandler, h='retry', m=['POST']), ]), route('/gears', GearsHandler), prefix('/gears', [ route('/<:[^/]+>', GearHandler), route('/<:[^/]+>/invocation', GearHandler, h='get_invocation'), route('/<:[^/]+>/suggest/<:[^/]+>/<:[^/]+>', GearHandler, h='suggest'), ]), route('/rules', RulesHandler), # Devices route( '/devices', DeviceHandler, h='get_all', m=['GET']), route( '/devices', DeviceHandler, m=['POST']), prefix('/devices', [ route('/status', DeviceHandler, h='get_status', m=['GET']), route('/self', DeviceHandler, h='get_self', m=['GET']), route('/<device_id:[^/]+>', DeviceHandler, m=['GET']), ]), # Groups route('/groups', GroupHandler, h='get_all', m=['GET']), route('/groups', GroupHandler, m=['POST']), route('/groups/<_id:{gid}>', GroupHandler, m=['GET', 'DELETE', 'PUT']), prefix('/<cont_name:groups>', [ route('/<cid:{gid}>/<list_name:roles>', ListHandler, m=['POST']), route('/<cid:{gid}>/<list_name:roles>/<site:{sid}>/<_id:{uid}>', ListHandler, m=['GET', 'PUT', 'DELETE']), route('/<cid:{gid}>/<list_name:tags>', TagsListHandler, m=['POST']), route('/<cid:{gid}>/<list_name:tags>/<value:{tag}>', TagsListHandler, m=['GET', 'PUT', 'DELETE']), ]), # Projects prefix('/projects', [ route('/groups', ContainerHandler, h='get_groups_with_project', m=['GET']), route('/recalc', ContainerHandler, h='calculate_project_compliance', m=['POST']), route('/<cid:{cid}>/template', ContainerHandler, h='set_project_template', m=['POST']), route('/<cid:{cid}>/template', ContainerHandler, h='delete_project_template', m=['DELETE']), route('/<cid:{cid}>/recalc', ContainerHandler, h='calculate_project_compliance', m=['POST']), ]), # Sessions route('/sessions/<cid:{cid}>/jobs', ContainerHandler, h='get_jobs', m=['GET']), # Collections route( '/collections', CollectionsHandler, h='get_all', m=['GET']), route( '/collections', CollectionsHandler, m=['POST']), prefix('/collections', [ route('/curators', CollectionsHandler, h='curators', m=['GET']), route('/<cid:{cid}>', CollectionsHandler, m=['GET', 'PUT', 'DELETE']), route('/<cid:{cid}>/sessions', CollectionsHandler, h='get_sessions', m=['GET']), route('/<cid:{cid}>/acquisitions', CollectionsHandler, h='get_acquisitions', m=['GET']), ]), # Collections / Projects prefix('/<cont_name:collections|projects>', [ prefix('/<cid:{cid}>', [ route('/<list_name:permissions>', PermissionsListHandler, m=['POST']), route('/<list_name:permissions>/<site:{sid}>/<_id:{uid}>', PermissionsListHandler, m=['GET', 'PUT', 'DELETE']), ]), ]), # Containers route('/<cont_name:{cname}>', ContainerHandler, name='cont_list', h='get_all', m=['GET']), route('/<cont_name:{cname}>', ContainerHandler, m=['POST']), prefix('/<cont_name:{cname}>', [ route('/<cid:{cid}>', ContainerHandler, m=['GET','PUT','DELETE']), prefix('/<cid:{cid}>', [ route('/<list_name:tags>', TagsListHandler, m=['POST']), route('/<list_name:tags>/<value:{tag}>', TagsListHandler, m=['GET', 'PUT', 'DELETE']), route('/packfile-start', FileListHandler, h='packfile_start', m=['POST']), route('/packfile', FileListHandler, h='packfile', m=['POST']), route('/packfile-end', FileListHandler, h='packfile_end'), route('/<list_name:files>', FileListHandler, m=['POST']), route('/<list_name:files>/<name:{fname}>', FileListHandler, m=['GET', 'DELETE']), route('/<list_name:analyses>', AnalysesHandler, m=['POST']), # Could be in a prefix. Had weird syntax highlighting issues so leaving for another day route('/<list_name:analyses>/<_id:{cid}>', AnalysesHandler, m=['GET', 'DELETE']), route('/<list_name:analyses>/<_id:{cid}>/files', AnalysesHandler, h='download', m=['GET']), route('/<list_name:analyses>/<_id:{cid}>/files/<name:{fname}>', AnalysesHandler, h='download', m=['GET']), route('/<list_name:analyses>/<_id:{cid}>/notes', AnalysesHandler, h='add_note', m=['POST']), route('/<list_name:analyses>/<_id:{cid}>/notes/<note_id:{cid}>', AnalysesHandler, h='delete_note', m=['DELETE']), route('/<list_name:notes>', NotesListHandler, m=['POST']), route('/<list_name:notes>/<_id:{nid}>', NotesListHandler, name='notes', m=['GET', 'PUT', 'DELETE']), ]) ]), # Misc (to be cleaned up later) route('/<par_cont_name:groups>/<par_id:{gid}>/<cont_name:projects>', ContainerHandler, h='get_all', m=['GET']), route('/<par_cont_name:{cname}>/<par_id:{cid}>/<cont_name:{cname}>', ContainerHandler, h='get_all', m=['GET']), # Multi - site route('/sites', CentralClient, h='sites', m=['GET']), route('/register', CentralClient, h='register', m=['POST']), ]), ] def dispatcher(router, request, response): try: if uwsgi is not None: uwsgi.set_logvar('request_id', request.id) except: # pylint: disable=bare-except request.logger.error("Error setting request_id log var", exc_info=True) try: rv = router.default_dispatcher(request, response) if rv is not None: response.write(json.dumps(rv, default=encoder.custom_json_serializer)) response.headers['Content-Type'] = 'application/json; charset=utf-8' except webapp2.HTTPException as e: util.send_json_http_exception(response, str(e), e.code) except Exception as e: # pylint: disable=broad-except request.logger.error("Error dispatching request", exc_info=True) if config.get_item('core', 'debug'): message = traceback.format_exc() else: message = 'Internal Server Error' util.send_json_http_exception(response, message, 500) def app_factory(*_, **__): # pylint: disable=protected-access,unused-argument # don't use config.get_item() as we don't want to require the database at startup application = webapp2.WSGIApplication(endpoints, debug=config.__config['core']['debug']) application.router.set_dispatcher(dispatcher) application.request_class = SciTranRequest if os.environ.get("SCITRAN_RUNTIME_COVERAGE") == "true": start_coverage() # configure new relic if config.__config['core']['newrelic']: try: import newrelic.agent, newrelic.api.exceptions newrelic.agent.initialize(config.__config['core']['newrelic']) application = newrelic.agent.WSGIApplicationWrapper(application) log.info('New Relic detected and loaded. Monitoring enabled.') except ImportError: log.critical('New Relic libraries not found.') sys.exit(1) except newrelic.api.exceptions.ConfigurationError: log.critical('New Relic detected, but configuration invalid.') sys.exit(1) return application # Functions to enable code coverage when API is started for testing def start_coverage(): import coverage config.log.info("Enabling code coverage") cov = coverage.coverage(source=["api"], data_suffix="integration-tests") cov.start() atexit.register(save_coverage, cov) def save_coverage(cov): config.log.info("Saving coverage") cov.stop() cov.save()