def container_post(request): ''' Proxies the grading result from inside container to A+ ''' sid = request.POST.get("sid", None) if not sid: return HttpResponseForbidden("Missing sid") meta = read_and_remove_submission_meta(sid) if meta is None: return HttpResponseForbidden("Invalid sid") #clean_submission_dir(meta["dir"]) data = { "points": int(request.POST.get("points", 0)), "max_points": int(request.POST.get("max_points", 1)), } for key in ["error", "grading_data"]: if key in request.POST: data[key] = request.POST[key] if "error" in data and data["error"].lower() in ("no", "false"): del data["error"] feedback = request.POST.get("feedback", "") # Fetch the corresponding exercise entry from the config. lang = meta["lang"] (course, exercise) = config.exercise_entry(meta["course_key"], meta["exercise_key"], lang=lang) if "feedback_template" in exercise: # replace the feedback with a rendered feedback template if the exercise is configured to do so # it is not feasible to support all of the old feedback template variables that runactions.py # used to have since the grading actions are not configured in the exercise YAML file anymore required_fields = { 'points', 'max_points', 'error', 'out' } result = MonitoredDict({ "points": data["points"], "max_points": data["max_points"], "out": feedback, "error": data.get("error", False), "title": exercise.get("title", ""), }) translation.activate(lang) feedback = template_to_str(course, exercise, None, exercise["feedback_template"], result=result) if result.accessed.isdisjoint(required_fields): alert = template_to_str( course, exercise, None, "access/feedback_template_did_not_use_result_alert.html") feedback = alert + feedback # Make unicode results ascii. feedback = feedback.encode("ascii", "xmlcharrefreplace") data["feedback"] = feedback if not post_data(meta["url"], data): write_submission_meta(sid, meta) return HttpResponse("Failed to deliver results", status=502) return HttpResponse("Ok")
def _acceptSubmission(request, course, exercise, post_url, sdir): ''' Queues the submission for grading. ''' uids = get_uid(request) attempt = int(request.GET.get("ordinal_number", 1)) container_flag = settings.CONTAINER_MODE and "container" in exercise # Backup synchronous grading. if not container_flag and not settings.CELERY_BROKER: LOGGER.warning("No queue configured") from grader.runactions import runactions r = runactions(course, exercise, sdir, uids, attempt) html = template_to_str(course, exercise, "", r["template"], r["result"]) return render_template( request, course, exercise, post_url, "access/async_accepted.html", { "synchronous": True, "accepted": True, "max_points": r["result"].get("max_points", 1), "points": r["result"].get("points", 0), "feedback": html, }) if "submission_url" in request.GET: surl = request.GET["submission_url"] surl_missing = False else: LOGGER.warning("submission_url missing from a request") surl = request.build_absolute_uri(reverse('test-result')) surl_missing = True _acceptSubmission.counter += 1 # Order container for grading. if container_flag: c = _requireContainer(exercise) course_extra = { "key": course["key"], "name": course["name"], } exercise_extra = { "key": exercise["key"], "title": exercise.get("title", None), } if exercise.get("personalized", False): exercise_extra["personalized_exercise"] \ = select_generated_exercise_instance(course, exercise, uids, attempt) if settings.ENABLE_PERSONAL_DIRECTORIES: exercise_extra["personal_directory"] \ = user_personal_directory_path(course, exercise, uids) sid = os.path.basename(sdir) write_submission_meta(sid, { "url": surl, "dir": sdir, }) r = invoke([ settings.CONTAINER_SCRIPT, sid, request.scheme + "://" + request.get_host(), c["image"], os.path.join(DIR, course["key"], c["mount"]), sdir, c["cmd"], json.dumps(course_extra), json.dumps(exercise_extra), ]) LOGGER.debug("Container order exit=%d out=%s err=%s", r["code"], r["out"], r["err"]) qlen = 1 # Queue in celery & rabbitmq for chroot sandbox actions. else: tasks.grade.delay(course["key"], exercise["key"], translation.get_language(), surl, sdir, uids, attempt) qlen = queue_length() LOGGER.debug("Submission of %s/%s, queue counter %d, queue length %d", course["key"], exercise["key"], _acceptSubmission.counter, qlen) if qlen >= settings.QUEUE_ALERT_LENGTH: LOGGER.error("Queue alert, length: %d", qlen) return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "accepted": True, "wait": True, "missing_url": surl_missing, "queue": qlen })
def _acceptSubmission(request, course, exercise, post_url, sdir: SubmissionDir): ''' Queues the submission for grading. ''' uids = get_uid(request) attempt = int(request.GET.get("ordinal_number", 1)) if "submission_url" in request.GET: surl = request.GET["submission_url"] surl_missing = False else: LOGGER.warning("submission_url missing from a request") surl = f'http://{request.META["HOSTNAME"]}:{request.META["SERVER_PORT"]}{reverse("test-result")}' surl_missing = True # Order container for grading. c = _requireContainer(exercise) ro_mounts = c.get("mounts", {}).copy() if not isinstance(ro_mounts, dict): raise ConfigError( "'container'->'mounts' must be a <path on course>-<mount path> dictionary or omitted altogether" ) for k, v in ro_mounts.items(): if not os.path.isabs(v): raise ConfigError(f"Mount path must be absolute: {v}") if any(v == p or v.startswith(p + "/") for p in ("/exercise", "/submission", "/personalized_exercise")): raise ConfigError( "/exercise, /submission and /personalized_exercise are reserved mounts, you cannot mount to them in 'mounts'" ) if os.path.isabs(k) or os.path.normpath(k).startswith(".."): raise ConfigError( f"Mounted path on course must be a relative subpath: {k}") ro_mounts[c["mount"]] = "/exercise" ro_mounts = { os.path.join(settings.COURSES_PATH, course["key"], k): v for k, v in ro_mounts.items() } if len(set(ro_mounts.values())) != len(ro_mounts): raise ConfigError("Mount paths must be distinct") if exercise.get("personalized", False): personalized_dir = select_generated_exercise_instance( course, exercise, uids, attempt) ro_mounts[personalized_dir] = "/personalized_exercise" write_submission_meta( sdir.sid, { "url": surl, "dir": str(sdir.dir()), "course_key": course["key"], "exercise_key": exercise["key"], "lang": translation.get_language(), }) return_code, out, err = runner_func( course=course, exercise=exercise, container_config=c, submission_id=sdir.sid, host_url=request.scheme + "://" + request.get_host(), readwrite_mounts={str(sdir.dir()): "/submission"}, readonly_mounts=ro_mounts, image=c["image"], cmd=c["cmd"], settings=settings.RUNNER_MODULE_SETTINGS, ) LOGGER.debug(f"Container order exit={return_code} out={out} err={err}") qlen = 1 return render_template( request, course, exercise, post_url, "access/async_accepted.html", { "error": return_code != 0, "accepted": True, "wait": True, "missing_url": surl_missing, "queue": qlen })
def _acceptSubmission(request, course, exercise, post_url, sdir): ''' Queues the submission for grading. ''' uids = get_uid(request) attempt = int(request.GET.get("ordinal_number", 1)) if "submission_url" in request.GET: surl = request.GET["submission_url"] surl_missing = False else: LOGGER.warning("submission_url missing from a request") surl = request.build_absolute_uri(reverse('test-result')) surl_missing = True _acceptSubmission.counter += 1 # Order container for grading. c = _requireContainer(exercise) course_extra = { "key": course["key"], "name": course["name"], } exercise_extra = { "key": exercise["key"], "title": exercise.get("title", None), "resources": c.get("resources", {}), # Unofficial param, implemented differently later "require_constant_environment": c.get("require_constant_environment", False) # Unofficial param, implemented differently later } if exercise.get("personalized", False): exercise_extra["personalized_exercise"] \ = select_generated_exercise_instance(course, exercise, uids, attempt) sid = os.path.basename(sdir) write_submission_meta( sid, { "url": surl, "dir": sdir, "course_key": course["key"], "exercise_key": exercise["key"], "lang": translation.get_language(), }) r = invoke([ settings.CONTAINER_SCRIPT, sid, request.scheme + "://" + request.get_host(), c["image"], os.path.join(DIR, course["key"], c["mount"]), sdir, c["cmd"], json.dumps(course_extra), json.dumps(exercise_extra), ]) LOGGER.debug("Container order exit=%d out=%s err=%s", r["code"], r["out"], r["err"]) qlen = 1 return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "accepted": True, "wait": True, "missing_url": surl_missing, "queue": qlen })
def _acceptSubmission(request, course, exercise, post_url, sdir): ''' Queues the submission for grading. ''' uids = get_uid(request) attempt = int(request.GET.get("ordinal_number", 1)) container_flag = settings.CONTAINER_MODE and "container" in exercise # Backup synchronous grading. if not container_flag and not settings.CELERY_BROKER: LOGGER.warning("No queue configured") from grader.runactions import runactions r = runactions(course, exercise, sdir, uids, attempt) html = template_to_str(course, exercise, "", r["template"], r["result"]) return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "synchronous": True, "accepted": True, "max_points": r["result"].get("max_points", 1), "points": r["result"].get("points", 0), "feedback": html, }) if "submission_url" in request.GET: surl = request.GET["submission_url"] surl_missing = False else: LOGGER.warning("submission_url missing from a request") surl = request.build_absolute_uri(reverse('test-result')) surl_missing = True _acceptSubmission.counter += 1 # Order container for grading. if container_flag: c = _requireContainer(exercise) course_extra = { "key": course["key"], "name": course["name"], } exercise_extra = { "key": exercise["key"], "title": exercise.get("title", None), "resources": c.get("resources", {}), # Unofficial param, implemented differently later "require_constant_environment": c.get("require_constant_environment", False) # Unofficial param, implemented differently later } if exercise.get("personalized", False): exercise_extra["personalized_exercise"] \ = select_generated_exercise_instance(course, exercise, uids, attempt) if settings.ENABLE_PERSONAL_DIRECTORIES: exercise_extra["personal_directory"] \ = user_personal_directory_path(course, exercise, uids) sid = os.path.basename(sdir) write_submission_meta(sid, { "url": surl, "dir": sdir, "course_key": course["key"], "exercise_key": exercise["key"], "lang": translation.get_language(), }) r = invoke([ settings.CONTAINER_SCRIPT, sid, request.scheme + "://" + request.get_host(), c["image"], os.path.join(DIR, course["key"], c["mount"]), sdir, c["cmd"], json.dumps(course_extra), json.dumps(exercise_extra), ]) LOGGER.debug("Container order exit=%d out=%s err=%s", r["code"], r["out"], r["err"]) qlen = 1 # Queue in celery & rabbitmq for chroot sandbox actions. else: tasks.grade.delay(course["key"], exercise["key"], translation.get_language(), surl, sdir, uids, attempt) qlen = queue_length() LOGGER.debug("Submission of %s/%s, queue counter %d, queue length %d", course["key"], exercise["key"], _acceptSubmission.counter, qlen) if qlen >= settings.QUEUE_ALERT_LENGTH: LOGGER.error("Queue alert, length: %d", qlen) return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "accepted": True, "wait": True, "missing_url": surl_missing, "queue": qlen })