def __init__(self, courseid): Course.__init__(self, courseid) if self._content.get('nofrontend', False): raise Exception("That course is not allowed to be displayed directly in the frontend") if "name" in self._content and "admins" in self._content and isinstance(self._content["admins"], list): self._name = self._content['name'] self._admins = self._content['admins'] self._accessible = AccessibleTime(self._content.get("accessible", None)) self._registration = AccessibleTime(self._content.get("registration", None)) self._registration_password = self._content.get('registration_password', None) self._registration_ac = self._content.get('registration_ac', None) if self._registration_ac not in [None, "username", "realname", "email"]: raise Exception("Course has an invalid value for registration_ac: " + courseid) self._registration_ac_list = self._content.get('registration_ac_list', []) else: raise Exception("Course has an invalid description: " + courseid)
def get_tasks(self): return OrderedDict(sorted(Course.get_tasks(self).items(), key=lambda t: t[1].get_order()))
def handle_job(self, job_id, course_id, task_id, inputdata, debug, _callback_status): """ Creates, executes and returns the results of a new job :param job_id: The distant job id :param course_id: The course id of the linked task :param task_id: The task id of the linked task :param inputdata: Input data, given by the student (dict) :param debug: A boolean, indicating if the job should be run in debug mode or not :param _callback_status: Not used, should be None. """ self.logger.info("Received request for jobid %s", job_id) internal_job_id = self._get_new_internal_job_id() self.logger.debug("New Internal job id -> %i", internal_job_id) # Initialize connection to Docker try: docker_connection = docker.Client(**kwargs_from_env()) except: self.logger.warning("Cannot connect to Docker!") return {'result': 'crash', 'text': 'Cannot connect to Docker'} # Get back the task data (for the limits) try: task = Course(course_id).get_task(task_id) except: self.logger.warning("Task %s/%s unavailable on this agent", course_id, task_id) return {'result': 'crash', 'text': 'Task unavailable on agent. Please retry later, the agents should synchronize soon. If the error ' 'persists, please contact your course administrator.'} limits = task.get_limits() mem_limit = limits.get("memory", 100) if mem_limit < 20: mem_limit = 20 environment = task.get_environment() if environment not in self.image_aliases: self.logger.warning("Task %s/%s ask for an unknown environment %s (not in aliases)", course_id, task_id, environment) return {'result': 'crash', 'text': 'Unknown container. Please contact your course administrator.'} environment = self.image_aliases[environment] # Remove possibly existing older folder and creates the new ones container_path = os.path.join(self.tmp_dir, str(internal_job_id)) # tmp_dir/id/ task_path = os.path.join(container_path, 'task') # tmp_dir/id/task/ sockets_path = os.path.join(container_path, 'sockets') # tmp_dir/id/socket/ student_path = os.path.join(task_path, 'student') # tmp_dir/id/task/student/ try: rmtree(container_path) except: pass os.mkdir(container_path) os.mkdir(sockets_path) os.chmod(container_path, 0777) os.chmod(sockets_path, 0777) copytree(os.path.join(common.base.get_tasks_directory(), task.get_course_id(), task.get_id()), task_path) os.chmod(task_path, 0777) if not os.path.exists(student_path): os.mkdir(student_path) os.chmod(student_path, 0777) # Run the container try: response = docker_connection.create_container( environment, stdin_open=True, volumes={'/task': {}, '/sockets': {}}, network_disabled=True ) container_id = response["Id"] # Start the RPyC server associated with this container container_set = set() student_container_management_service = self._get_agent_student_container_service( container_set, student_path, task.get_environment(), limits.get("time", 30), mem_limit) # Small workaround for error "AF_UNIX path too long" when the agent is launched inside a container. Resolve all symlinks to reduce the # path length. smaller_path_to_socket = os.path.realpath(os.path.join(sockets_path, 'INGInious.sock')) student_container_management = UnixSocketServer( student_container_management_service, socket_path=smaller_path_to_socket, protocol_config={"allow_public_attrs": True, 'allow_pickle': True}) student_container_management_thread = threading.Thread(target=student_container_management.start) student_container_management_thread.daemon = True student_container_management_thread.start() # Start the container docker_connection.start(container_id, binds={os.path.abspath(task_path): {'ro': False, 'bind': '/task'}, os.path.abspath(sockets_path): {'ro': False, 'bind': '/sockets'}}, mem_limit=mem_limit * 1024 * 1024, memswap_limit=mem_limit * 1024 * 1024, # disable swap oom_kill_disable=True) # Send the input data container_input = {"input": inputdata, "limits": limits} if debug: container_input["debug"] = True docker_connection.attach_socket(container_id, {'stdin': 1, 'stream': 1}).send(json.dumps(container_input) + "\n") except Exception as e: self.logger.warning("Cannot start container! %s", str(e)) rmtree(container_path) return {'result': 'crash', 'text': 'Cannot start container'} # Ask the "cgroup" thread to verify the timeout/memory limit self._timeout_watcher.add_container_timeout(container_id, limits.get("time", 30), limits.get('hard_time', limits.get("time", 30) * 3)) self._memory_watcher.add_container_memory_limit(container_id, mem_limit) # Wait for completion error_occured = False try: return_value = docker_connection.wait(container_id, limits.get('hard_time', limits.get("time", 30) * 4)) if return_value == -1: raise Exception('Container crashed!') except: self.logger.info("Container for job id %s crashed", job_id) error_occured = True # Verify that everything went well error_timeout = self._timeout_watcher.container_had_error(container_id) error_memory = self._memory_watcher.container_had_error(container_id) if error_timeout: result = {"result": "timeout"} elif error_memory: result = {"result": "overflow"} elif error_occured: result = {"result": "crash", "text": "An unknown error occurred while running the container"} else: # Get logs back try: stdout = str(docker_connection.logs(container_id, stdout=True, stderr=False)) result = json.loads(stdout) except Exception as e: print e self.logger.warning("Cannot get back stdout of container %s!", container_id) result = {'result': 'crash', 'text': 'The grader did not return a readable output'} # Close RPyC server student_container_management.close() # Remove container thread.start_new_thread(docker_connection.remove_container, (container_id, True, False, True)) # Remove subcontainers for i in container_set: # Also deletes them from the timeout/memory watchers self._timeout_watcher.container_had_error(container_id) self._memory_watcher.container_had_error(container_id) thread.start_new_thread(docker_connection.remove_container, (i, True, False, True)) # Delete folders rmtree(container_path) # Return! return result