Index: appengine/chrome_infra_mon_proxy/main.py |
diff --git a/appengine/chrome_infra_mon_proxy/main.py b/appengine/chrome_infra_mon_proxy/main.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8a59500b79d1941aca31a7f5e70083f23a0d4e5f |
--- /dev/null |
+++ b/appengine/chrome_infra_mon_proxy/main.py |
@@ -0,0 +1,120 @@ |
+# Copyright 2015 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import logging |
+import random |
+import os |
+import time |
+import sys |
+import webapp2 |
+ |
+from protorpc import messages |
+from protorpc import message_types |
+from protorpc import remote |
+ |
+from google.appengine.api import app_identity, taskqueue |
+ |
+import admin_handler |
+import common |
+ |
+BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
+sys.path.insert(0, os.path.join(BASE_DIR, 'components', 'third_party')) |
+ |
+from components import auth |
+ |
+VM_MODULES = ['vm1', 'vm2', 'vm3'] |
ghost stip (do not use)
2015/04/14 00:36:42
oppan dijkstra style:
VM_MODULE_COUNT = 3
VM_MODU
Sergey Berezin (google)
2015/04/16 04:39:07
I feel the temptation :-) But this suggests the nu
|
+ |
+ |
+def is_group_member(group_name): |
+ """Skip authorization for dev appserver.""" |
+ if common.is_development_server(): |
+ auth_decorator = auth.public |
+ else: |
+ auth_decorator = auth.require( # pragma: no branch |
+ lambda: auth.is_group_member(group_name)) |
+ |
+ def decorator(fn): |
+ return auth_decorator(fn) |
+ return decorator |
+ |
+ |
+class NoBackendException(Exception): |
+ pass |
+ |
+ |
+class LoadBalancer(object): |
+ """Balance the load among VM modules. |
+ |
+ TODO(sergeyberezin): take into account health checks on the |
+ corresponding NAT boxes. Specifically, fetch the health status |
+ from the datastore in __init__(), and update it periodically as needed. |
+ """ |
+ def __init__(self): |
+ # dict: module name -> bool: whether module is healthy. |
+ self._health = {module: True for module in VM_MODULES} |
+ # Seconds since UNIX epoch when last health check was made. |
ghost stip (do not use)
2015/04/14 00:36:42
probably want to remove now until we figure out ho
Sergey Berezin (google)
2015/04/16 04:39:07
Done.
|
+ self._last_health_check = 0 |
+ |
+ def _healthy_modules(self): |
+ """Return the list of names of all healthy modules.""" |
+ # TODO(sergeyberezin): perform health checks and update the |
+ # datastore once in a while. |
+ return [module for module in VM_MODULES if self._health[module]] |
+ |
+ def choose_module(self): |
+ """Select a module to send the data to.""" |
+ modules = self._healthy_modules() |
+ if not modules: |
+ raise NoBackendException('Error: no healthy backends are available') |
+ return random.randint(0, len(modules)-1) |
ghost stip (do not use)
2015/04/14 00:36:42
return random.choice(modules)
Sergey Berezin (google)
2015/04/16 04:39:06
Done. Nice, thanks!
|
+ |
+ |
+def forward_data(data): |
+ """Forwards the raw data to the backend.""" |
+ # logging.debug('Forwarding data: %s', common.payload_stats(data)) |
ghost stip (do not use)
2015/04/14 00:36:42
why commented out? seems legit
Sergey Berezin (google)
2015/04/16 04:39:06
It computes md5, and now that I debugged the heade
|
+ # Task queue should work correctly both in dev and prod server. |
+ lb = LoadBalancer() |
+ queue_name = VM_MODULES[lb.choose_module()] |
ghost stip (do not use)
2015/04/14 00:36:42
queue_name = lb.choose_module()
Sergey Berezin (google)
2015/04/16 04:39:07
Done.
|
+ logging.info('Selected queue: %s', queue_name) |
+ taskqueue.add(url='/vm', payload=data, queue_name=queue_name) |
+ |
+ |
+class MonacqHandler(auth.AuthenticatingHandler): |
+ # Disable XSRF in local dev appserver; otherwise requests will fail. |
+ if common.is_development_server(): |
+ xsrf_token_enforce_on = [] # pragma: no cover |
ghost stip (do not use)
2015/04/14 00:36:43
nit: two spaces before comment
Sergey Berezin (google)
2015/04/16 04:39:07
Done.
|
+ |
+ @is_group_member('service-account-monitoring-proxy') |
+ def get(self): |
+ self.response.headers.add_header('Content-Type', 'text/plain') |
+ self.response.write('GET requests are not allowed.') |
ghost stip (do not use)
2015/04/14 00:36:42
I think if you don't provide a get function to beg
Sergey Berezin (google)
2015/04/16 04:39:06
Indeed - removed.
|
+ self.response.set_status(403) |
ghost stip (do not use)
2015/04/14 00:36:42
at least make it 405
Sergey Berezin (google)
2015/04/16 04:39:07
N/A - removed def get().
|
+ return |
+ |
+ @is_group_member('service-account-monitoring-proxy') |
+ def post(self): |
+ # logging.info('Received POST /monacq: %s', |
+ # common.payload_stats(self.request.body)) |
+ forward_data(self.request.body) |
+ |
+ |
+class MainHandler(webapp2.RequestHandler): |
+ def get(self): |
+ # TODO(sergeyberezin): create a meaningful welcome page with some |
+ # documentation. |
+ msg = 'There is nothing here yet.\n' |
ghost stip (do not use)
2015/04/14 00:36:42
make this a little more friendly!
msg = 'Welcome
Sergey Berezin (google)
2015/04/16 04:39:06
Done - created an actual page with some useful con
|
+ self.response.headers['Content-Type'] = 'text/plain' |
ghost stip (do not use)
2015/04/14 00:36:42
is this needed?
Sergey Berezin (google)
2015/04/16 04:39:07
Nope - it's all gone now.
|
+ self.response.out.write(msg) |
+ |
+ |
+ |
+logging.basicConfig(level=logging.DEBUG) |
+ |
+main_handlers = [ |
+ (r'/', MainHandler), |
+ (r'/monacq', MonacqHandler), |
+ (r'/admin/(.*)', admin_handler.AdminDispatch), |
+] |
+ |
+app = webapp2.WSGIApplication(main_handlers, debug=True) |