Example #1
0
def drop_batch_job(batch_job_id):
    """ Delete a **finished** batch job from the database """
    job = get_database().batch_jobs.find_one({"_id": ObjectId(batch_job_id)})
    if "result" not in job:
        raise Exception("Batch job is still running, cannot delete it")
    get_database().batch_jobs.remove({"_id": ObjectId(batch_job_id)})
    if "file" in job["result"]:
        get_gridfs().delete(job["result"]["file"])
Example #2
0
def add_job(task, inputdata, debug=False):
    """ Add a job in the queue and returns a submission id.
        task is a Task instance and inputdata is the input as a dictionary
        If debug is true, more debug data will be saved
    """
    if not User.is_logged_in():
        raise Exception("A user must be logged in to submit an object")

    username = User.get_username()
    course = FrontendCourse(task.get_course_id())

    obj = {
        "courseid": task.get_course_id(),
        "taskid": task.get_id(),
        "input": get_gridfs().put(
            json.dumps(inputdata)),
        "status": "waiting",
        "submitted_on": datetime.now()}

    if course.is_group_course() and username not in course.get_staff(True):
        group = get_database().groups.find_one({"course_id": task.get_course_id(), "users": username})
        obj.update({"username": group["users"]})
    else:
        obj.update({"username": [username]})

    submissionid = get_database().submissions.insert(obj)

    PluginManager.get_instance().call_hook("new_submission", submissionid=submissionid, submission=obj, inputdata=inputdata)

    get_job_manager().new_job(task, inputdata, (lambda job: _job_done_callback(submissionid, task, job)), "Frontend - {}".format(username), debug)

    return submissionid
Example #3
0
def _job_done_callback(submissionid, task, job):
    """ Callback called by JobManager when a job is done. Updates the submission in the database with the data returned after the completion of the job """
    submission = get_submission(submissionid, False)
    submission = get_input_from_submission(submission)

    job = _parse_text(task, job)

    data = {
        "status": ("done" if job["result"] == "success" or job["result"] == "failed" else "error"),  # error only if error was made by INGInious
        "result": job["result"],
        "grade": job["grade"],
        "text": job.get("text", None),
        "tests": job.get("tests", None),
        "problems": (job["problems"] if "problems" in job else {}),
        "archive": (get_gridfs().put(base64.b64decode(job["archive"])) if "archive" in job else None)
    }

    # Store additional data
    dont_dump = ["task", "course", "input"]
    for index in job:
        if index not in data and index not in dont_dump:
            data[index] = job[index]

    # Save submission to database
    get_database().submissions.update(
        {"_id": submission["_id"]},
        {"$set": data}
    )

    for username in submission["username"]:
        UserData(username).update_stats(submission, job)

    PluginManager.get_instance().call_hook("submission_done", submission=submission, job=job)
def add_job(task, inputdata, debug=False):
    """ Add a job in the queue and returns a submission id.
        task is a Task instance and inputdata is the input as a dictionary
        If debug is true, more debug data will be saved
    """
    if not User.is_logged_in():
        raise Exception("A user must be logged in to submit an object")

    username = User.get_username()

    jobid = get_job_manager().new_job_id()

    obj = {
        "username": username,
        "courseid": task.get_course_id(),
        "taskid": task.get_id(),
        "input": get_gridfs().put(
            json.dumps(inputdata)),
        "status": "waiting",
        "jobid": jobid,
        "submitted_on": datetime.now()}
    submissionid = get_database().submissions.insert(obj)

    PluginManager.get_instance().call_hook("new_submission", submissionid=submissionid, submission=obj, jobid=jobid, inputdata=inputdata)

    get_job_manager().new_job(task, inputdata, job_done_callback, "Frontend - {}".format(username), jobid, debug)

    return submissionid
Example #5
0
def get_input_from_submission(submission, only_input=False):
    """ Get the input of a submission. If only_input is False, returns the full submissions with a dictionnary object at the key "input". Else, returns only the dictionnary. """
    if isinstance(submission.get("input", {}), dict):
        if only_input:
            return submission.get("input", {})
        else:
            return submission
    else:
        inp = json.load(get_gridfs().get(submission['input']))
        if only_input:
            return inp
        else:
            submission["input"] = inp
            return submission
Example #6
0
    def GET(self, courseid, bid, path=""):
        """ GET request """

        course, _ = get_course_and_check_rights(courseid)
        batch_job = get_batch_job_status(bid)

        if batch_job is None:
            raise web.notfound()

        if "result" not in batch_job or "file" not in batch_job["result"]:
            raise web.notfound()

        f = get_gridfs().get(batch_job["result"]["file"])

        #hack for index.html:
        if path == "/":
            path = "/index.html"

        if path == "":
            web.header('Content-Type', 'application/x-gzip', unique=True)
            web.header('Content-Disposition', 'attachment; filename="' + bid + '.tar.gz"', unique=True)
            return f.read()
        else:
            path = path[1:] #remove the first /
            if path.endswith('/'):  # remove the last / if it exists
                path = path[0:-1]

            try:
                tar = tarfile.open(fileobj=f, mode='r:gz')
                file_info = tar.getmember(path)
            except:
                raise web.notfound()

            if file_info.isdir(): #tar.gz the dir and return it
               tmp = tempfile.TemporaryFile()
               new_tar = tarfile.open(fileobj=tmp,mode='w:gz')
               for m in tar.getmembers():
                   new_tar.addfile(m, tar.extractfile(m))
               new_tar.close()
               tmp.seek(0)
               return tmp
            elif not file_info.isfile():
                raise web.notfound()
            else: #guess a mime type and send it to the browser
                to_dl = tar.extractfile(path).read()
                mimetypes.init()
                mime_type = mimetypes.guess_type(urllib.pathname2url(path))
                web.header('Content-Type', mime_type[0])
                return to_dl
Example #7
0
    def GET(self, courseid, bid):
        """ GET request """

        course, _ = get_course_and_check_rights(courseid)
        batch_job = get_batch_job_status(bid)

        if batch_job is None:
            raise web.notfound()

        done = False
        submitted_on = batch_job["submitted_on"]
        container_name = batch_job["container_name"]
        container_title = container_name
        container_description = ""

        file_list = None
        retval = 0
        stdout = ""
        stderr = ""

        try:
            container_metadata = get_batch_container_metadata(container_name)
            if container_metadata == (None, None, None):
                container_title = container_metadata[0]
                container_description = container_metadata[1]
        except:
            pass

        if "result" in batch_job:
            done = True
            retval = batch_job["result"]["retval"]
            stdout = batch_job["result"].get("stdout","")
            stderr = batch_job["result"].get("stderr", "")

            if "file" in batch_job["result"]:
                f = get_gridfs().get(batch_job["result"]["file"])
                try:
                    tar = tarfile.open(fileobj=f,mode='r:gz')
                    file_list = set(tar.getnames()) - set([''])
                    tar.close()
                except:
                    pass
                finally:
                    f.close()

        return renderer.course_admin.batch_summary(course, bid, done, container_name, container_title, container_description, submitted_on,
                                                   retval, stdout, stderr, file_list)
Example #8
0
def _batch_job_done_callback(batch_job_id, result):
    """ Called when the batch job with id jobid has finished.
        result is a dictionnary, containing:

        - {"retval": 0, "stdout": "...", "stderr": "...", "file": "..."}
            if everything went well.(where file is a tgz file containing the content of the / output folder from the container)
        - {"retval": "...", "stdout": "...", "stderr": "..."}
            if the container crashed (retval is an int != 0)
        - {"retval": -1, "stderr": "the error message"}
            if the container failed to start
    """

    # If there is a tgz file to save, put it in gridfs
    if "file" in result:
        result["file"] = get_gridfs().put(result["file"].read())

    # Save submission to database
    get_database().batch_jobs.update(
        {"_id": batch_job_id},
        {"$set": {"result": result}}
    )
Example #9
0
def get_submission_archive(submissions, sub_folders):
    """
    :param submissions: a list of submissions
    :param sub_folders: possible values:
        []: put all submissions in /
        ['taskid']: put all submissions for each task in a different directory /taskid/
        ['username']: put all submissions for each user in a different directory /username/
        ['taskid','username']: /taskid/username/
        ['username','taskid']: /username/taskid/
    :return: a file-like object containing a tgz archive of all the submissions
    """
    tmpfile = tempfile.TemporaryFile()
    tar = tarfile.open(fileobj=tmpfile, mode='w:gz')

    for submission in submissions:
        submission = get_input_from_submission(submission)

        # Compute base path in the tar file
        base_path = "/"
        for sub_folder in sub_folders:
            if sub_folder == 'taskid':
                base_path = submission['taskid'] + '/' + base_path
            elif sub_folder == 'username':
                base_path = submission['username'] + '/' + base_path

        submission_yaml = StringIO.StringIO(common.custom_yaml.dump(submission).encode('utf-8'))
        submission_yaml_fname = base_path + str(submission["_id"]) + '.test'
        info = tarfile.TarInfo(name=submission_yaml_fname)
        info.size = submission_yaml.len
        info.mtime = time.mktime(submission["submitted_on"].timetuple())

        # Add file in tar archive
        tar.addfile(info, fileobj=submission_yaml)

        # If there is an archive, add it too
        if 'archive' in submission and submission['archive'] is not None and submission['archive'] != "":
            subfile = get_gridfs().get(submission['archive'])
            taskfname = base_path + str(submission["_id"]) + '.tgz'

            # Generate file info
            info = tarfile.TarInfo(name=taskfname)
            info.size = subfile.length
            info.mtime = time.mktime(submission["submitted_on"].timetuple())

            # Add file in tar archive
            tar.addfile(info, fileobj=subfile)

        # If there files that were uploaded by the student, add them
        if submission['input'] is not None:
            for pid, problem in submission['input'].iteritems():
                # If problem is a dict, it is a file (from the specification of the problems)
                if isinstance(problem, dict):
                    # Get the extension (match extensions with more than one dot too)
                    DOUBLE_EXTENSIONS = ['.tar.gz', '.tar.bz2', '.tar.bz', '.tar.xz']
                    if not problem['filename'].endswith(tuple(DOUBLE_EXTENSIONS)):
                        _, ext = os.path.splitext(problem['filename'])
                    else:
                        for t_ext in DOUBLE_EXTENSIONS:
                            if problem['filename'].endswith(t_ext):
                                ext = t_ext

                    subfile = StringIO.StringIO(base64.b64decode(problem['value']))
                    taskfname = base_path + str(submission["_id"]) + '_uploaded_files/' + pid + ext

                    # Generate file info
                    info = tarfile.TarInfo(name=taskfname)
                    info.size = subfile.len
                    info.mtime = time.mktime(submission["submitted_on"].timetuple())

                    # Add file in tar archive
                    tar.addfile(info, fileobj=subfile)

    # Close tarfile and put tempfile cursor at 0
    tar.close()
    tmpfile.seek(0)
    return tmpfile
    def download_submission_set(self, submissions, filename, sub_folders):
        """ Create a tar archive with all the submissions """
        if len(submissions) == 0:
            raise web.notfound(renderer.notfound("There's no submission that matches your request"))
        try:
            tmpfile = tempfile.TemporaryFile()
            tar = tarfile.open(fileobj=tmpfile, mode='w:gz')

            for submission in submissions:
                submission = get_input_from_submission(submission)

                # Compute base path in the tar file
                base_path = "/"
                for sub_folder in sub_folders:
                    if sub_folder == 'taskid':
                        base_path = submission['taskid'] + '/' + base_path
                    elif sub_folder == 'username':
                        base_path = submission['username'] + '/' + base_path

                submission_yaml = StringIO.StringIO(common.custom_yaml.dump(submission).encode('utf-8'))
                submission_yaml_fname = base_path + str(submission["_id"]) + '.test'
                info = tarfile.TarInfo(name=submission_yaml_fname)
                info.size = submission_yaml.len
                info.mtime = time.mktime(submission["submitted_on"].timetuple())

                # Add file in tar archive
                tar.addfile(info, fileobj=submission_yaml)

                # If there is an archive, add it too
                if 'archive' in submission and submission['archive'] is not None and submission['archive'] != "":
                    subfile = get_gridfs().get(submission['archive'])
                    taskfname = base_path + str(submission["_id"]) + '.tgz'

                    # Generate file info
                    info = tarfile.TarInfo(name=taskfname)
                    info.size = subfile.length
                    info.mtime = time.mktime(submission["submitted_on"].timetuple())

                    # Add file in tar archive
                    tar.addfile(info, fileobj=subfile)

                # If there files that were uploaded by the student, add them
                if submission['input'] is not None:
                    for pid, problem in submission['input'].iteritems():
                        # If problem is a dict, it is a file (from the specification of the problems)
                        if isinstance(problem, dict):
                            # Get the extension (match extensions with more than one dot too)
                            DOUBLE_EXTENSIONS = ['.tar.gz', '.tar.bz2', '.tar.bz', '.tar.xz']
                            if not problem['filename'].endswith(tuple(DOUBLE_EXTENSIONS)):
                                _, ext = os.path.splitext(problem['filename'])
                            else:
                                for t_ext in DOUBLE_EXTENSIONS:
                                    if problem['filename'].endswith(t_ext):
                                        ext = t_ext

                            subfile = StringIO.StringIO(base64.b64decode(problem['value']))
                            taskfname = base_path + str(submission["_id"]) + '_uploaded_files/' + pid + ext

                            # Generate file info
                            info = tarfile.TarInfo(name=taskfname)
                            info.size = subfile.len
                            info.mtime = time.mktime(submission["submitted_on"].timetuple())

                            # Add file in tar archive
                            tar.addfile(info, fileobj=subfile)

            # Close tarfile and put tempfile cursor at 0
            tar.close()
            tmpfile.seek(0)

            web.header('Content-Type', 'application/x-gzip', unique=True)
            web.header('Content-Disposition', 'attachment; filename="' + filename + '"', unique=True)
            return tmpfile
        except Exception as e:
            print e
            raise web.notfound()