From 5a6f25f300a2921328c85a53717d288b69167476 Mon Sep 17 00:00:00 2001 From: Nick Daly Date: Wed, 23 May 2012 20:53:57 -0500 Subject: [PATCH] Added a querying API. --- ugly_hacks/santiago/protocols/https.py | 66 ++++++++++++++++++++++++-- ugly_hacks/santiago/santiago.py | 43 +++++++++++++++-- 2 files changed, 100 insertions(+), 9 deletions(-) diff --git a/ugly_hacks/santiago/protocols/https.py b/ugly_hacks/santiago/protocols/https.py index 8668e9fe4..5ba4b46f6 100644 --- a/ugly_hacks/santiago/protocols/https.py +++ b/ugly_hacks/santiago/protocols/https.py @@ -1,4 +1,19 @@ -"""The HTTPS Santiago listener and sender.""" +"""The HTTPS Santiago listener and sender. + +FIXME: use a reasonable RESTful API. + +- https://appmecha.wordpress.com/2008/10/27/cherrypy-gae-routing-2/ +- http://tools.cherrypy.org/wiki/RestfulDispatch +- http://docs.cherrypy.org/dev/refman/_cpdispatch.html +- http://www.infoq.com/articles/rest-introduction +- http://www.infoq.com/articles/rest-anti-patterns +- http://stackoverflow.com/a/920181 +- http://docs.cherrypy.org/dev/progguide/REST.html + +It's been about five times too long since I've looked at this sort of +thing. Stupid everything-is-GET antipattern. + +""" from santiago import SantiagoListener, SantiagoSender @@ -7,6 +22,14 @@ import httplib, urllib, urlparse import sys import logging +def jsonify_tool_callback(*args, **kwargs): + response = cherrypy.response + response.headers['Content-Type'] = 'application/json' + response.body = encoder.iterencode(response.body) + +if cherrypy.__version__ < "3.2": + cherrypy.tools.json_out = cherrypy.Tool('before_finalize', jsonify_tool_callback, priority=30) + class Listener(SantiagoListener): def __init__(self, santiago, socket_port=0, @@ -38,18 +61,53 @@ class Listener(SantiagoListener): logging.exception(e) @cherrypy.expose - def query(self, host, service): + def learn(self, host, service): """Request a resource from another Santiago client. TODO: add request whitelisting. """ - if not cherrypy.request.remote.ip.startswith("127.0.0"): + # TODO enforce restfulness, POST, and build a request form. + # if not cherrypy.request.method == "POST": + # return + + if not cherrypy.request.remote.ip.startswith("127.0.0."): logging.debug("protocols.https.query: Request from non-local IP") return - self.santiago.query(host, service) + return super(Listener, self).learn(host, service) + @cherrypy.expose + @cherrypy.tools.json_out() + def where(self, host, service): + """Show where a host is providing me services.""" + + if not cherrypy.request.remote.ip.startswith("127.0.0."): + logging.debug("protocols.https.query: Request from non-local IP") + return + + return list(super(Listener, self).where(host, service)) + + @cherrypy.expose + def provide(self, client, service, location): + """Provide a service for the client at the location.""" + + if not cherrypy.request.remote.ip.startswith("127.0.0."): + logging.debug("protocols.https.query: Request from non-local IP") + return + + return super(Listener, self).provide(client, service, location) + + @cherrypy.expose + def pdb(self): + """Set a trace.""" + + if not cherrypy.request.remote.ip.startswith("127.0.0."): + logging.debug("protocols.https.query: Request from non-local IP") + return + + import pdb; pdb.set_trace() + class Sender(SantiagoSender): def __init__(self, santiago, proxy_host, proxy_port): diff --git a/ugly_hacks/santiago/santiago.py b/ugly_hacks/santiago/santiago.py index 3a9019b9e..3a32b8b06 100644 --- a/ugly_hacks/santiago/santiago.py +++ b/ugly_hacks/santiago/santiago.py @@ -171,6 +171,11 @@ class Santiago(object): def learn_service(self, host, service, locations): """Learn a service somebody else hosts for me.""" + if service not in self.consuming: + self.consuming[service] = dict() + + if host not in self.consuming[service]: + self.consuming[service][host] = set() if locations: self.consuming[service][host] = ( @@ -179,8 +184,15 @@ class Santiago(object): def provide_service(self, client, service, locations): """Start hosting a service for somebody else.""" + if client not in self.hosting: + self.hosting[client] = dict() + + if service not in self.hosting[client]: + self.hosting[client][service] = set() + if locations: - self.hosting[client][service].union(locations) + self.hosting[client][service] = ( + self.hosting[client][service] | locations) def get_host_locations(self, client, service): """Return where I'm hosting the service for the client. @@ -201,7 +213,6 @@ class Santiago(object): except KeyError as e: logging.exception(e) - def query(self, host, service): """Request a service from another Santiago. @@ -464,6 +475,7 @@ class Santiago(object): self.requests[host].remove(service) # clean buffers + # TODO clean up after 5 minutes to allow all hosts to reply? if not self.requests[host]: del self.requests[host] @@ -598,6 +610,29 @@ class SantiagoListener(SantiagoConnector): def incoming_request(self, request): self.santiago.incoming_request(request) + def where(self, host, service): + """Return where the named host provides me a service. + + If no service is provided, return None. + + TODO: unittest + + """ + return self.santiago.get_client_locations(host, service) + + def learn(self, host, service): + """Request a service from another Santiago client. + + TODO: add request whitelisting. + + """ + return self.santiago.query(host, service) + + def provide(self, client, service, location): + """Provide a service for the client at the location.""" + + return self.santiago.provide_service(client, service, set([location])) + class SantiagoSender(SantiagoConnector): """Generic Santiago Sender superclass. @@ -621,11 +656,9 @@ if __name__ == "__main__": "ssl_private_key": cert }, } senders = { "https": { "proxy_host": "localhost", "proxy_port": 8118} } - hosting = { mykey: { "santiago": set( ["https://localhost:8080", - "https://somestuff" ] )}} + hosting = { mykey: { "santiago": set( ["https://localhost:8080"] )}} consuming = { "santiago": { mykey: set( ["https://localhost:8080"] )}} - # load the Santiago santiago = Santiago(listeners, senders, hosting, consuming, me=mykey)