diff options
author | Lars Wirzenius <lars.wirzenius@codethink.co.uk> | 2014-01-20 14:24:27 +0000 |
---|---|---|
committer | Lars Wirzenius <lars.wirzenius@codethink.co.uk> | 2014-04-15 13:29:27 +0000 |
commit | 4fc162b07b2e9d8489e16ed647e5d96f5c66e10a (patch) | |
tree | ac2a2a5b86a5d789bd28b383851b28d7f293b928 /lorry-controller-webapp | |
parent | 716ad28c18ac00c52797dc42c843569b1834fb88 (diff) | |
download | lorry-controller-4fc162b07b2e9d8489e16ed647e5d96f5c66e10a.tar.gz |
Add new Lorry Controller
Diffstat (limited to 'lorry-controller-webapp')
-rwxr-xr-x | lorry-controller-webapp | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/lorry-controller-webapp b/lorry-controller-webapp new file mode 100755 index 0000000..239e988 --- /dev/null +++ b/lorry-controller-webapp @@ -0,0 +1,223 @@ +#!/usr/bin/env python +# +# Copyright (C) 2014 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import logging +import os +import wsgiref.simple_server + +import bottle +import cliapp +from flup.server.fcgi import WSGIServer + + +import lorrycontroller + + +class WEBAPP(cliapp.Application): + + def add_settings(self): + self.settings.string( + ['statedb'], + 'use FILE as the state database', + metavar='FILE') + + self.settings.string( + ['configuration-directory'], + 'use DIR as the configuration directory', + metavar='DIR', + default='.') + + self.settings.string( + ['confgit-url'], + 'get CONFGIT from URL', + metavar='URL') + + self.settings.string( + ['confgit-branch'], + 'get git branch BRANCH in CONFGIT', + metavar='URL', + default='master') + + self.settings.boolean( + ['debug-real-confgit'], + 'if true, do real git operations on the configuration directory; ' + 'if false, do no git operations on it and just what is there', + default=True) + + self.settings.string( + ['status-html'], + 'write a static HTML page to FILE to describe overall status', + metavar='FILE', + default='/dev/null') + + self.settings.boolean( + ['wsgi'], + 'run in wsgi mode (default is debug mode, for development)') + + self.settings.integer( + ['debug-port'], + 'use PORT in debugging mode ' + '(i.e., when not running under WSGI); ' + 'note that using this to non-zero disables --debug-port-file', + metavar='PORT', + default=0) + + self.settings.string( + ['debug-port-file'], + 'write listening port to FILE when in debug mode ' + '(i.e., not running under WSGI)', + metavar='FILE', + default='webapp.port') + + self.settings.string( + ['debug-host'], + 'listen on HOST when in debug mode (i.e., not running under WSGI)', + metavar='HOST', + default='0.0.0.0') + + self.settings.string_list( + ['debug-fake-trove'], + 'fake access to remote Troves (to do gitano ls, etc) ' + 'using local files: get ls listing for TROVE from $PATH, ' + 'where PATH names a file in JSON with the necessary info; ' + 'may be used multiple times', + metavar='TROVE=PATH') + + self.settings.string( + ['templates'], + 'find HTML page templates (*.tpl) in DIR', + metavar='DIR', + default='/usr/share/lorry-controller/templates') + + self.settings.string( + ['static-files'], + 'server static files from DIR', + metavar='DIR', + default='/usr/share/lorry-controller/static') + + def find_routes(self): + '''Return all classes that are API routes. + + This is a generator. + + ''' + + # This is a bit tricky and magic. globals() returns a dict + # that contains all objects in the global namespace. We + # iterate over the objects and pick the ones that are + # subclasses of our superclass (no duck typing here), but ARE + # NOT the superclass itself. + + for name in dir(lorrycontroller): + x = getattr(lorrycontroller, name) + is_route = ( + type(x) == type and # it must be class, for issubclass + issubclass(x, lorrycontroller.LorryControllerRoute) and + x != lorrycontroller.LorryControllerRoute) + if is_route: + yield x + + def process_args(self, args): + self.settings.require('statedb') + + self.setup_proxy() + + templates = self.load_templates() + + webapp = bottle.Bottle() + + for route_class in self.find_routes(): + route = route_class(self.settings, templates) + webapp.route( + path=route.path, + method=route.http_method, + callback=route.run) + + logging.info('Starting server') + if self.settings['wsgi']: + self.run_wsgi_server(webapp) + else: + self.run_debug_server(webapp) + + def load_templates(self): + templates = {} + for basename in os.listdir(self.settings['templates']): + if basename.endswith('.tpl'): + name = basename[:-len('.tpl')] + pathname = os.path.join(self.settings['templates'], basename) + with open(pathname) as f: + templates[name] = f.read() + return templates + + def run_wsgi_server(self, webapp): + WSGIServer(webapp).run() + + def run_debug_server(self, webapp): + if self.settings['debug-port']: + self.run_debug_server_on_given_port(webapp) + else: + self.run_debug_server_on_random_port(webapp) + + def run_debug_server_on_given_port(self, webapp): + bottle.run( + webapp, + host=self.settings['debug-host'], + port=self.settings['debug-port'], + quiet=True, + debug=True) + + def run_debug_server_on_random_port(self, webapp): + server_port_file = self.settings['debug-port-file'] + + class DebugServer(wsgiref.simple_server.WSGIServer): + '''WSGI-like server that uses an ephemeral port. + + Rather than use a specified port, or default, the + DebugServer connects to an ephemeral port and writes + its number to debug-port-file, so a non-racy temporary + port can be used. + + ''' + + def __init__(self, (host, port), *args, **kwargs): + wsgiref.simple_server.WSGIServer.__init__( + self, (host, 0), *args, **kwargs) + with open(server_port_file, 'w') as f: + f.write(str(self.server_port) + '\n') + + bottle.run( + webapp, + host=self.settings['debug-host'], + server_class=DebugServer, + quiet=True, + debug=True) + + def setup_proxy(self): + """Tell urllib2 to use a proxy for http action by lorry-controller. + + Load the proxy information from the JSON file given by proxy_def, then + set urllib2's url opener to open urls via an authenticated proxy. + + """ + + config_filename = os.path.join( + self.settings['configuration-directory'], 'proxy.conf') + lorrycontroller.setup_proxy(config_filename) + + +WEBAPP().run() |