diff options
author | Prabhu Ramachandran | 2015-09-29 13:45:06 +0530 |
---|---|---|
committer | Prabhu Ramachandran | 2015-09-29 13:45:06 +0530 |
commit | ebbf135af98720f1979cd28a9108817bac385ce7 (patch) | |
tree | e812ac5466ad043f867c58bc363df823522a8468 /yaksh/code_server.py | |
parent | 31f5e743031d105b0406e9587dc33bb065cd6e4d (diff) | |
parent | 53be4bc2ec40a9a84ff5ea73db2fbea0a07f5338 (diff) | |
download | online_test-ebbf135af98720f1979cd28a9108817bac385ce7.tar.gz online_test-ebbf135af98720f1979cd28a9108817bac385ce7.tar.bz2 online_test-ebbf135af98720f1979cd28a9108817bac385ce7.zip |
Merge pull request #56 from ankitjavalkar/examtime
Start and Expiry times for Quizzes
Diffstat (limited to 'yaksh/code_server.py')
-rwxr-xr-x | yaksh/code_server.py | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/yaksh/code_server.py b/yaksh/code_server.py new file mode 100755 index 0000000..2593752 --- /dev/null +++ b/yaksh/code_server.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python +"""This server runs an XMLRPC server that can be submitted code and tests +and returns the output. It *should* be run as root and will run as the user +'nobody' so as to minimize any damange by errant code. This can be configured +by editing settings.py to run as many servers as desired. One can also +specify the ports on the command line. Here are examples:: + + $ sudo ./code_server.py + # Runs servers based on settings.py:SERVER_PORTS one server per port given. + +or:: + + $ sudo ./code_server.py 8001 8002 8003 8004 8005 + # Runs 5 servers on ports specified. + +All these servers should be running as nobody. This will also start a server +pool that defaults to port 50000 and is configurable in +settings.py:SERVER_POOL_PORT. This port exposes a `get_server_port` function +that returns an available server. +""" +import sys +from SimpleXMLRPCServer import SimpleXMLRPCServer +import pwd +import os +import stat +from os.path import isdir, dirname, abspath, join, isfile +import signal +from multiprocessing import Process, Queue +import subprocess +import re +import json +# Local imports. +from settings import SERVER_PORTS, SERVER_POOL_PORT +from language_registry import set_registry, get_registry + + +MY_DIR = abspath(dirname(__file__)) + + +# Private Protocol ########## +def run_as_nobody(): + """Runs the current process as nobody.""" + # Set the effective uid and to that of nobody. + nobody = pwd.getpwnam('nobody') + os.setegid(nobody.pw_gid) + os.seteuid(nobody.pw_uid) + + +############################################################################### +# `CodeServer` class. +############################################################################### +class CodeServer(object): + """A code server that executes user submitted test code, tests it and + reports if the code was correct or not. + """ + def __init__(self, port, queue): + self.port = port + self.queue = queue + + # Public Protocol ########## + def check_code(self, language, json_data, in_dir=None): + """Calls relevant EvaluateCode class based on language to check the + answer code + """ + code_evaluator = self._create_evaluator_instance(language, json_data, + in_dir) + result = code_evaluator.evaluate() + + # Put us back into the server pool queue since we are free now. + self.queue.put(self.port) + + return json.dumps(result) + + def run(self): + """Run XMLRPC server, serving our methods.""" + server = SimpleXMLRPCServer(("localhost", self.port)) + self.server = server + server.register_instance(self) + self.queue.put(self.port) + server.serve_forever() + + # Private Protocol ########## + def _create_evaluator_instance(self, language, json_data, in_dir): + """Create instance of relevant EvaluateCode class based on language""" + set_registry() + registry = get_registry() + cls = registry.get_class(language) + instance = cls.from_json(language, json_data, in_dir) + return instance + + +############################################################################### +# `ServerPool` class. +############################################################################### +class ServerPool(object): + """Manages a pool of CodeServer objects.""" + def __init__(self, ports, pool_port=50000): + """Create a pool of servers. Uses a shared Queue to get available + servers. + + Parameters + ---------- + + ports : list(int) + List of ports at which the CodeServer's should run. + + pool_port : int + Port at which the server pool should serve. + """ + self.my_port = pool_port + self.ports = ports + queue = Queue(maxsize=len(ports)) + self.queue = queue + servers = [] + for port in ports: + server = CodeServer(port, queue) + servers.append(server) + p = Process(target=server.run) + p.start() + self.servers = servers + + # Public Protocol ########## + + def get_server_port(self): + """Get available server port from ones in the pool. This will block + till it gets an available server. + """ + q = self.queue + was_waiting = True if q.empty() else False + port = q.get() + if was_waiting: + print '*'*80 + print "No available servers, was waiting but got server \ + later at %d." % port + print '*'*80 + sys.stdout.flush() + return port + + def run(self): + """Run server which returns an available server port where code + can be executed. + """ + server = SimpleXMLRPCServer(("localhost", self.my_port)) + self.server = server + server.register_instance(self) + server.serve_forever() + + +############################################################################### +def main(args=None): + run_as_nobody() + if args: + ports = [int(x) for x in args[1:]] + else: + ports = SERVER_PORTS + + server_pool = ServerPool(ports=ports, pool_port=SERVER_POOL_PORT) + server_pool.run() + +if __name__ == '__main__': + args = sys.argv[1:] + main(args) |