From 42076870fd819e43cea9ad4ffe39228155c6c80b Mon Sep 17 00:00:00 2001 From: Peter Ward Date: Tue, 9 Sep 2014 22:34:51 +1000 Subject: start work on server and client --- robots/client.py | 62 +++++++++++++++++++++++++ robots/server.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ simple.py | 2 +- 3 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 robots/client.py create mode 100644 robots/server.py 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() -- cgit v1.2.3