summaryrefslogtreecommitdiff
path: root/lorry-controller-webapp
diff options
context:
space:
mode:
authorLars Wirzenius <lars.wirzenius@codethink.co.uk>2014-01-20 14:24:27 +0000
committerLars Wirzenius <lars.wirzenius@codethink.co.uk>2014-04-15 13:29:27 +0000
commit4fc162b07b2e9d8489e16ed647e5d96f5c66e10a (patch)
treeac2a2a5b86a5d789bd28b383851b28d7f293b928 /lorry-controller-webapp
parent716ad28c18ac00c52797dc42c843569b1834fb88 (diff)
downloadlorry-controller-4fc162b07b2e9d8489e16ed647e5d96f5c66e10a.tar.gz
Add new Lorry Controller
Diffstat (limited to 'lorry-controller-webapp')
-rwxr-xr-xlorry-controller-webapp223
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()