summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Ward <peteraward@gmail.com>2014-09-09 22:34:51 +1000
committerPeter Ward <peteraward@gmail.com>2014-09-09 22:34:51 +1000
commit42076870fd819e43cea9ad4ffe39228155c6c80b (patch)
tree8aaac2cdd68de0860867975d1698145131cbd966
parent85b84bf04b654b7075f821536dcd25b85aa9cea4 (diff)
start work on server and client
-rw-r--r--robots/client.py62
-rw-r--r--robots/server.py135
-rw-r--r--simple.py2
3 files changed, 198 insertions, 1 deletions
diff --git a/robots/client.py b/robots/client.py
new file mode 100644
index 0000000..7d60ba2
--- /dev/null
+++ b/robots/client.py
@@ -0,0 +1,62 @@
+import os
+from threading import Thread
+import time
+import uuid
+import zmq
+
+import logbook
+
+import network
+
+SERVER_KIND = 'robots'
+
+class Browser(object):
+ def __init__(self, client, ctx=None):
+ if ctx is None:
+ ctx = zmq.Context.instance()
+
+ self.client = client
+ self.running = False
+ self.targets = {}
+ self.ctx = ctx
+
+ def update(self, services):
+ current_targets = set()
+
+ for service in services:
+ target = service['target']
+ current_targets.add(target)
+
+ if target in self.targets:
+ continue
+
+ sock = self.ctx.socket(zmq.REQ)
+ sock.connect(target)
+ sock.send_json({'action': 'list'})
+ response = sock.recv_json()
+
+ if response['status'] == 200:
+ self.targets[target] = {
+ 'name': str(service['name']),
+ 'bots': response['bots'],
+ }
+
+ for target in list(self.targets.keys()):
+ if target not in current_targets:
+ del self.targets[target]
+
+ network.browser.clear_terminal()
+ for info in self.targets.values():
+ print(info['name'] + ':')
+ for name in info['bots']:
+ print(' * ' + name)
+ print()
+
+ def run(self, client_run):
+ client_run()
+
+client = network.Client(SERVER_KIND)
+client.find_server(
+ browser_cls=Browser,
+ connect=False
+)
diff --git a/robots/server.py b/robots/server.py
new file mode 100644
index 0000000..759766c
--- /dev/null
+++ b/robots/server.py
@@ -0,0 +1,135 @@
+from functools import wraps
+import json
+import os
+import uuid
+
+import logbook
+
+import network
+
+log = logbook.Logger(__name__)
+
+def bad_request(msg):
+ return {'status': 400, 'message': msg}
+
+def not_found(msg):
+ return {'status': 404, 'message': msg}
+
+def server_error(msg):
+ return {'status': 500, 'message': msg}
+
+def success(**kwargs):
+ rv = {'status': 200}
+ rv.update(kwargs)
+ return rv
+
+def json_handler(handle):
+ SERVER_ERROR = json.dumps(server_error('Unexpected server error.')).encode('utf-8')
+
+ @wraps(handle)
+ def handler(data):
+ try:
+ request = json.loads(data.decode('utf-8'))
+ except ValueError:
+ log.exception('Bad JSON data: {!r}', request)
+ try:
+ response = handle(request)
+ data = json.dumps(response).encode('utf-8')
+ except Exception:
+ log.exception('Exception in handler.')
+ data = SERVER_ERROR
+ return data
+
+ return handler
+
+def get_options(fn):
+ arguments = fn.__code__.co_varnames
+ defaults = fn.__defaults__
+
+ required = arguments[:-len(defaults)]
+ optional = list(zip(defaults, arguments[-len(defaults):]))
+ return (required, optional)
+
+class Server(object):
+ SERVER_NAME = os.getenv('NAME')
+ SERVER_KIND = 'robots'
+
+ def __init__(self):
+ # Maps bot names to a function which will return the bot callable.
+ # (or, you know, a class)
+ self.bots = {}
+
+ # Maps instance ids to a bot callable.
+ self.instances = {}
+
+ def add_bot(self, fn, name=None):
+ if name is None:
+ name = fn.__name__
+ self.bots[name] = fn
+
+ def add_simple_bot(self, fn, name=None):
+ if name is None:
+ name = fn.__name__
+ return self.add_bot(lambda: fn, name)
+
+ def list_bots(self):
+ bots = {
+ name: None and get_options(fn)
+ for name, fn in self.bots.items()
+ }
+ return success(bots=bots)
+
+ def create(self, name, options):
+ try:
+ bot_class = self.bots[name]
+ except KeyError:
+ message = 'Could not find bot %s' % (name,)
+ log.warning(message)
+ return not_found(message)
+
+ try:
+ instance = bot_class(**options)
+ except Exception:
+ message = 'Could not create bot %s' % (name,)
+ log.exception(message)
+ return server_error(message)
+
+ instance_id = str(uuid.uuid4())
+ self.instances[instance_id] = instance
+ log.debug('Created new bot instance %s' % instance_id)
+ return success(instance_id=instance_id)
+
+ def handle(self, request):
+ try:
+ action = request['action']
+
+ if action == 'list':
+ return self.list_bots()
+
+ elif action == 'create':
+ name = request['name']
+ options = request['options']
+ return self.create(name, options)
+
+ elif action == 'process':
+ instance_id = request['instance_id']
+ state = request['state']
+ return self.next_move(instance_id, state)
+
+ elif action == 'destroy':
+ instance_id = request['instance_id']
+ return self.destroy(instance_id)
+
+ else:
+ return bad_request('Unrecognised action %s' % (action,))
+
+ except KeyError as e:
+ return bad_request('No %s specified' % e.args)
+
+ def run(self):
+ server = network.Server(
+ self.SERVER_NAME,
+ self.SERVER_KIND,
+ handler=json_handler(self.handle),
+ )
+ server.run()
diff --git a/simple.py b/simple.py
index 554016c..f8a7edd 100644
--- a/simple.py
+++ b/simple.py
@@ -12,4 +12,4 @@ def random_walk(whoami, state):
if __name__ == '__main__':
server = robots.Server()
server.add_simple_bot(random_walk, 'Alice')
- server.run(debug=True)
+ server.run()