def main(dir): """Unpacks archive and invokes executor""" # reads assignment config with open(join(dir, 'config')) as handle: config = ConfigParser.RawConfigParser() config.readfp(handle) # reads vmchecker_storer.ini with open(join(dir, 'storer')) as handle: storer = ConfigParser.RawConfigParser() storer.readfp(handle) # copies files to where vmchecker expects them (wtf # XXX 'executor_jobs' path is hardcoded in executor ejobs = vmcheckerpaths.abspath('executor_jobs') # cleans up executor_jobs, if not already clean if isdir(ejobs): shutil.rmtree(ejobs) os.mkdir(ejobs) shutil.copy( # copies assignment join(dir, 'archive.zip'), vmcheckerpaths.abspath('executor_jobs', 'file.zip')) shutil.copy( # copies tests join(dir, 'tests.zip'), vmcheckerpaths.abspath('executor_jobs', 'tests.zip')) assignment = config.get('Assignment', 'Assignment') # yet another hack section = assignments._SECTION_PREFIX + assignment machine = storer.get(section, 'Machine') timeout = storer.get(section, 'Timeout') kernel_messages = storer.get(section, 'KernelMessages') _run_executor(ejobs, machine, assignment, timeout, kernel_messages) try: _run_callback(dir, ejobs) except: _logger.exception('cannot run callback') # clears files shutil.rmtree(ejobs) _logger.info('all done')
def check_tester_setup_correctly(): # check needed paths setup correctly for path in vmcheckerpaths.tester_paths(): if not os.path.isdir(path): _logger.error('"%s" missing. Run `make tester-dist`!', path) exit(1) # check binaries build # TODO: XXX: Hardcoded # VMExecutor is expected to die soon :) if not os.path.isfile(os.path.join(vmcheckerpaths.abspath('VMExecutor'), 'vm_executor')): _logger.error('VMExecutor/vm_executor missing. Run `make tester-dist`!') exit(1)
def include(self, assignment): """An iterator over the files to include when submitting an assignment. The iterators yields pairs (destination, source) where destination is the name of the file in the archive source is the name of the file on the disk relative to vmchecker root The include options is useful to include other scripts and configuration files. """ self._check_valid(assignment) for option in self.__assignments[assignment]: if option.startswith(_INCLUDE_PREFIX): yield (option[len(_INCLUDE_PREFIX):], vmcheckerpaths.abspath(self.get(assignment, option)))
def create_storer_git_repo(): """Creates the repo for the assignments on the storer.""" # first make teh destination directory rel_repo_path = vmcheckerpaths.repository abs_repo_path = vmcheckerpaths.abspath(rel_repo_path) _mkdir_if_not_exist(abs_repo_path) # then, if missing, initialize a git repo in it. repo_path_git = os.path.join(abs_repo_path, '.git') if not(os.path.isdir(repo_path_git)): # no git repo found in the dir. try: env = os.environ env['GIT_DIR'] = repo_path_git check_call(['git', 'init'], env=env) except CalledProcessError: logging.error('cannot create git repo in %s' % repo_path_git)
def _run_executor(ejobs, machine, assignment, timeout, kernel_messages): """Starts a job. XXX lots of wtf per minute XXX parsing config should be executors' job """ args = [ # '/bin/echo', vmcheckerpaths.abspath('VMExecutor/vm_executor'), machine, kernel_messages, # enables kernel_messages config.get(machine, 'VMPath'), config.get('Global', 'LocalAddress'), # did I review commander.cpp? config.get(machine, 'GuestUser'), config.get(machine, 'GuestPassword'), # XXX keys? config.get(machine, 'GuestBasePath'), config.get(machine, 'GuestShellPath'), config.get(machine, 'GuestHomeInBash'), # why is this needed? vmcheckerpaths.root, assignment, timeout, ] _logger.info('Begin homework evaluation') _logger.debug('calling %s', args) start = time.time() # first just try to open the process try: popen = Popen(args) except Exception: _logger.exception('Cannot invoke VMExecutor.') with open(join(ejobs, 'job_errors'), 'a') as handler: print >> handler, 'Cannot run VMExecutor.' print >> handler, 'Please contact the administrators.' # if we cannot open the process, there is nothing more to be done return # waits for the the process to finish try: counter = 0 deadline = start + int(timeout) + _EXECUTOR_OVERHEAD while time.time() < deadline: counter += 1 exit_code = popen.poll() if exit_code is None: # if process has not finished => continue to sleep _logger.debug('-- VMExecutor sleeping for 5 seconds, ' 'exit_code is None: x=%d', counter) # polls every 5 seconds time.sleep(5) else: with open(join(ejobs, 'job_errors'), 'a') as handler: print >> handler, 'VMExecutor returned %d (%s)' % ( exit_code, ['success', 'error'][exit_code < 0]) # no reason to stay in the loop after process exit terminates popen = None return else: _logger.error("VMChecker timeouted on assignment `%s' " "running on machine `%s'.", assignment, machine) with open(join(ejobs, 'job_errors'), 'a') as handler: print >> handler, """\ VMExecutor successfuly started, but it's taking too long. Check your sources, makefiles, etc and resubmit. If the problem persists please contact administrators.""" except: _logger.exception('Exception after starting VMExecutor.') with open(join(ejobs, 'job_errors'), 'a') as handler: print >> handler, """\ Error after starting VMExecutor. If the problem persists please contact administrators.""" finally: # release any leftover resources try: if popen: popen.kill() except: pass
def submit_assignment(assignment_config): """Submits config file for evaluation. This function creates a zip archive, stores it in $VMCHECKER_ROOT/unchecked/ directory and calls submit script. The archive contains: config - assignment config (eg. name, time of submission etc) global - global assignments config (eg. deadlines) archive.zip - a zip containing the homework callback - a script executed by the tester to send results back """ assignment_config = abspath(assignment_config) # reads user, assignment and course aconfig = ConfigParser.RawConfigParser() with open(assignment_config) as handler: aconfig.readfp(handler) user = aconfig.get('Assignment', 'User') assignment = aconfig.get('Assignment', 'Assignment') course = config.get(assignment, 'Course') # location of student's homework archive = join(dirname(assignment_config), 'archive.zip') assert isfile(archive), "Missing archive `%s'" % archive # location of tests tests = join(vmcheckerpaths.dir_tests(), assignment + '.zip') assert isfile(tests), "Missing tests `%s'" % tests # builds archive with configuration with _Locker(assignment): # creates the zip archive with an unique name fd = mkstemp( suffix='.zip', prefix='%s_%s_%s_' % (course, assignment, user), dir=vmcheckerpaths.dir_unchecked()) _logger.info("Creating zip package at `%s'", fd[1]) # populates the archive # Includes at least these files: # config -> homework config (see above) # archive.zip -> homework files (student's sources) # tests.zip -> assignment tests try: with os.fdopen(fd[0], 'w+b') as handler: zip = ZipFile(handler, 'w') zip.write(assignment_config, 'config') # assignment config zip.write(archive, 'archive.zip') # assignment archive zip.write(tests, 'tests.zip') # the archive containing tests # includes extra required files for f in config.get(assignment): if not f.startswith('include '): continue dst, src = f[8:], config.get(assignment, f) src = vmcheckerpaths.abspath(src) assert isfile(src), "`%s' is missing" % src zip.write(src, dst) _logger.debug("Included `%s' as `%s'.", src, dst) zip.close() except: _logger.error("Failed to create archive `%s'", fd[1]) os.unlink(fd[1]) raise # sends homework to tester submit = vmcheckerpaths.abspath(config.get(assignment, 'Submit')) _logger.info('Calling submission script %s', submit) try: check_call((submit, fd[1])) except: _logger.fatal("Cannot submit homework. Archive `%s' not deleted.", fd[1]) raise
def submit_homework(location): """Submits homework at location for evaluation This function creates a zip archive in the ./unchecked/ directory and calls the submit script. The archive contains: config - assignment config (eg. name, time of submission etc) archive.zip - a zip containing the homework tests.zip - a zip containing the tests callback - a script executed by the tester to send results back ... - assignment's extra files (see assignments.Assignments.include()) """ # reads user, assignment and course # hrc = homework resource configuration hrc = ConfigParser.RawConfigParser() with open(os.path.join(location, "config")) as handler: hrc.readfp(handler) assignment = hrc.get("Assignment", "Assignment") user = hrc.get("Assignment", "User") course = config.assignments.course(assignment) # location of student's homework # XXX should create a clean zip from the repository archive = os.path.join(location, "archive.zip") assert os.path.isfile(archive), "Missing archive %s" % archive # location of tests tests = config.assignments.tests(assignment) assert os.path.isfile(tests), "Missing tests %s" % tests # builds archive with configuration with config.assignments.lock(assignment): # creates the zip archive with an unique name fd = tempfile.mkstemp( suffix=".zip", prefix="%s_%s_%s_" % (course, assignment, user), dir=vmcheckerpaths.dir_unchecked() ) # FIXME not here _logger.info("Creating zip package %s", fd[1]) # populates the archive (see the function's docstring) try: with os.fdopen(fd[0], "w+b") as handler: zip_ = zipfile.ZipFile(handler, "w") zip_.write(os.path.join(location, "config"), "config") zip_.write(archive, "archive.zip") zip_.write(tests, "tests.zip") # includes extra required files for dest, src in config.assignments.include(assignment): src = vmcheckerpaths.abspath(src) # XXX do not assert, but raise assert os.path.isfile(src), "File %s is missing" % src zip_.write(src, dest) _logger.debug("Included %s as %s", src, dest) zip_.close() except: _logger.error("Failed to create zip archive %s", fd[1]) os.unlink(fd[1]) raise # package created, sends homework to tester by invoking submission script submit = config.assignments.get(assignment, "Submit") submit = vmcheckerpaths.abspath(submit) _logger.info("Invoking submission script %s", submit) try: subprocess.check_call((submit, fd[1])) except: _logger.fatal("Cannot submit homework %s, %s", assignment, user) os.unlink(fd[1]) raise
def path(section, option): """Returns an absolute path derived from an option""" return vmcheckerpaths.abspath(config.get(section, option))