#!/usr/bin/env python3 # -*- coding: utf-8 -*- #from runner.celery import app import subprocess import os import sys import shutil import json import importlib.machinery WORK_DIR = "/data/run/jobs" OER_FORMATS_PATH = "/data/oer/exercise-formats/" def load_handler(path): handler = {'name': None, 'loader': None, 'mod': None} #print(path, file=sys.stderr) for root, dirs, files in os.walk(path): #print(dirs, file=sys.stderr) #print(files, file=sys.stderr) if 'controller.py' in files: print("INFO: controller.py present for '%s'!" % path, file=sys.stderr) try: #name = root.replace(path, '') name = path loader = importlib.machinery.SourceFileLoader(name, root + '/controller.py') mod = loader.load_module() handler = {'name': name, 'loader': loader, 'mod': mod} except: print("WARNING: Loading controller failed for '%s'!" % path, file=sys.stderr) else: print("INFO: No controller found for '%s'!" % path, file=sys.stderr) return handler class Job(object): """ The job class for tasks implements some default behaivior for every format and tries to call special event handlers when they are defined for the formats or exercises. The lifecycle of a job in its current form is as follows: 1) class initialisation 2) import_handlers try to find handler by looking for controller.py in format and exercise 3) unpack 4) build: compile, default by calling make 5) execute: run the code 6) grade: run provided test scripts 7) response: provide output, msg and grade """ def __init__(self, job): #print("Job.__init__()", file=sys.stderr) self.data = job self.work_path = WORK_DIR + '/' + str(job['_id']) + '/' self.handler_format = {'name': None, 'loader': None, 'mod': None} self.handler_exercise = {'name': None, 'loader': None, 'mod': None} self.build_result = None self.execute_result = None self.grade_result = 'FAIL' self.response_data = {} def import_handlers(self): print("Job.init_handlers()", file=sys.stderr) # find exercise specific and default handler for exercise format self.handler_format = load_handler("%s/%s" % (OER_FORMATS_PATH, self.data['type'])) self.handler_exercise = load_handler(self.data['source_path']) if self.handler_format != None: print("INFO: Format handler found ;", file=sys.stderr) if self.handler_exercise != None: print("INFO: Exercise handlers found ;", file=sys.stderr) def extract(self): #print("Job.extract()", file=sys.stderr) # creates workdir and files print(self.data['source_path'], file=sys.stderr) print(self.work_path, file=sys.stderr) #os.makedirs(self.work_path) src = self.data['source_path'] dst = self.work_path shutil.copytree(src, dst) self.sourcefile = self.work_path + 'program.c' self.programfile = self.work_path + 'program' # overwrite default with user submission source = open(self.sourcefile, 'w') source.write(self.data['solution']) source.close() def build(self): print("Job.build()", file=sys.stderr) # Try to call type specific handler or fallback to default action if 'build' in dir(self.handler_exercise['mod']): self.build_result = self.handler_exercise['mod'].build(self) elif 'build' in dir(self.handler_format['mod']): self.build_result = self.handler_format['mod'].build(self) else: print("INFO: build() handler not found!", file=sys.stderr) self.build_result = False def execute(self): print("Job.execute()", file=sys.stderr) # Try to call type specific handler or fallback to default action if 'execute' in dir(self.handler_exercise['mod']): self.execute_result = self.handler_exercise['mod'].execute(self) elif 'execute' in dir(self.handler_format['mod']): self.execute_result = self.handler_format['mod'].execute(self) else: print("INFO: execute() handler not found!", file=sys.stderr) self.execute_result = False def grade(self): print("Job.grade()", file=sys.stderr) # Try to call type specific handler or fallback to default action if 'grade' in dir(self.handler_exercise['mod']): print("Job.grade() exercise", file=sys.stderr) self.grade_result = self.handler_exercise['mod'].grade(self) elif 'grade' in dir(self.handler_format['mod']): print("Job.grade() format", file=sys.stderr) self.grade_result = self.handler_format['mod'].grade(self) else: print("INFO: grade() handler not found!", file=sys.stderr) #self.grade_result = 'PASS' # default to PASS so the course does not break? self.grade_result = 'FAIL' # default to FAIL so student can not proceed in case there was a minor error with the course def response(self): print("Job.response()", file=sys.stderr) result = None # Try to call type specific handler or fallback to default action if 'response' in dir(self.handler_exercise['mod']): print("Job.response() exercise", file=sys.stderr) print(self.handler_exercise, file=sys.stderr) return self.handler_exercise['mod'].response(self) elif 'response' in dir(self.handler_format['mod']): print("Job.response() format", file=sys.stderr) print(self.handler_format, file=sys.stderr) return self.handler_format['mod'].response(self) else: print("INFO: response() handler not found!", file=sys.stderr) output = "" grade = self.grade_result msg = "" data = self.response_data return {'output': output, 'grade': grade, 'msg': msg, 'data': data} def clean(self): print("Job.cleanup()", file=sys.stderr) shutil.rmtree(self.work_path) pass def store_result(self): # into file self.jobfile = self.work_path + 'job.json' # overwrite default with user submission fp = open(self.jobfile, 'w') json.dump({"job": self.data, "response": self.response_data}, fp) fp.close() # into mongodb def get_file_content(self, filename): try: with open(self.work_path + '/' + filename, 'r') as f: content = f.read().strip() return content except: print('WARNING: Failed to read "%s", serving empty string.' % (filename), file=sys.stderr) return "" #@app.task(time_limit=20) #def timelimited_task(): # try: # return do_work() # except SoftTimeLimitExceeded: # cleanup_in_a_hurry() # #@app.task #def compile(): # return True #@app.task(bind=True) def compile_and_run(id, data): #print(data, file=sys.stderr) #print(data['type'], file=sys.stderr) #print(data['action'], file=sys.stderr) #print(data['source_path'], file=sys.stderr) data['_id'] = id # create job object and call the different handlers j = Job(data) j.import_handlers() if data['action'] not in ["quiz"]: j.extract() j.build() if data['action'] in ["test", "execute", "run"]: j.execute() if data['action'] in ["grade", "quiz"]: j.grade() #print("Result prepare") ret = j.response() j.response_data = ret j.store_result() #print("Result sent!") return ret