package de.hftstuttgart.rest.v1.task; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.dockerjava.api.model.Bind; import com.github.dockerjava.api.model.Volume; import de.hftstuttgart.models.LegacyMoodleResult; import de.hftstuttgart.models.ModocotResultSummary; import de.hftstuttgart.rest.v1.unittest.UnitTestUpload; import de.hftstuttgart.utils.DockerUtil; import de.hftstuttgart.utils.FileUtil; import de.hftstuttgart.utils.JGitUtil; import de.hftstuttgart.utils.UnifiedTicketingUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.servlet.annotation.MultipartConfig; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Rest controller for everything related to the TASK files */ @RestController @RequestMapping("/v1/task") @MultipartConfig public class TaskUpload { private static final Logger LOG = LogManager.getLogger(TaskUpload.class); private final JGitUtil jGitUtil; private final DockerUtil dockerUtil; private final String assignmentBasePath; private final Path testTmpPathHost; private final Path testTmpPathModocot; public TaskUpload( Environment env, JGitUtil jGitUtil, DockerUtil dockerUtil ) { this.jGitUtil = jGitUtil; this.dockerUtil = dockerUtil; // set base path for assignments to be stored Path p = Paths.get( env.getProperty("modocot.dir"), env.getProperty("modocot.dir.test.folder.name")); this.assignmentBasePath = p.toAbsolutePath().toString(); // set path of temporary directory on host and inside our container this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir")); this.testTmpPathModocot = Paths.get(env.getProperty("modocot.tests.tmp.dir")); } @RequestMapping(method = RequestMethod.POST) public LegacyMoodleResult uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef, @RequestParam("assignmentId") String assignmentId ) throws IOException, InterruptedException { LOG.info("submission for testing received"); LOG.debug("creating new temporary directory"); Path workDirectory = Files.createTempDirectory(testTmpPathModocot, "modocot"); LOG.debug(String.format("working dir for test is: %s", workDirectory.toAbsolutePath().toString())); // define paths for the test, the submission and where the result is to be expected afterwards Path testPath = Paths.get(workDirectory.toAbsolutePath().toString(), "test"); Path srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "src"); Path resultPath = Paths.get(workDirectory.toAbsolutePath().toString(), "result"); // clone stored test to tmpdir LOG.debug("copying pre-downloaded unitttest repo"); FileUtil.copyFolder( Paths.get( assignmentBasePath, assignmentId ), testPath ); LOG.debug("copy test config"); Files.copy( Paths.get( assignmentBasePath, assignmentId + ".txt" ), Paths.get( workDirectory.toAbsolutePath().toString(), "config.txt" ) ); // clone student code to tmpdir Pattern pattern = Pattern.compile(UnitTestUpload.modocotDueConfigRegex); Matcher config = null; LOG.debug("reading task file"); // open received file in a try-with try (BufferedReader br = new BufferedReader( new InputStreamReader( taskFileRef.getInputStream()))) { String line; // as long as we haven't found a configuration and have lines left, search while (config == null && (line = br.readLine()) != null) { Matcher matcher = pattern.matcher(line); if (matcher.matches()) { LOG.debug(String.format("found modocot line: %s", line)); config = matcher; } } } catch (IOException e) { LOG.error("Error while reading repo config", e); } finally { if (config == null) { throw new RuntimeException("couldn't find repo config for unittest clone"); } } LOG.debug("calling repo clone"); jGitUtil.cloneRepository(config, srcPath.toAbsolutePath().toString()); Files.createDirectory(resultPath); LOG.info("starting unittest"); pattern = Pattern.compile(UnitTestUpload.modocotTestConfigRegex); config = null; LOG.debug("reading test config"); // open test config and read it in again, important to know with which docker image the test should be run try (BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile())))) { String line; while (config == null && (line = br.readLine()) != null) { Matcher matcher = pattern.matcher(line); if (matcher.matches()) { LOG.debug(String.format("found modocot line: %s", line)); config = matcher; } } } catch (IOException e) { LOG.error("Error while reading test config", e); } finally { if (config == null) { throw new RuntimeException("couldn't find test config for unittest"); } } // define the paths to mount as Binds from Host to the test-container Path testPathHost = Paths.get( testTmpPathHost.toAbsolutePath().toString(), workDirectory.getName(workDirectory.getNameCount()-1).toString(), testPath.getName(testPath.getNameCount()-1).toString() ); Path srcPathHost = Paths.get( testTmpPathHost.toAbsolutePath().toString(), workDirectory.getName(workDirectory.getNameCount()-1).toString(), srcPath.getName(srcPath.getNameCount()-1).toString() ); Path resultPathHost = Paths.get( testTmpPathHost.toAbsolutePath().toString(), workDirectory.getName(workDirectory.getNameCount()-1).toString(), resultPath.getName(resultPath.getNameCount()-1).toString() ); // start test-container with professor given image and bind mounts for test, submission and result dockerUtil.runContainer( config.group(4), new Bind(testPathHost.toAbsolutePath().toString(), new Volume("/modocot/test")), new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/modocot/src")), new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/modocot/result")) ); // define expected result file File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile(); // check if result file is there if (!resultFile.exists() || !resultFile.isFile()) { LOG.error(String.format("couln't find result file in %s", resultFile.getAbsolutePath())); throw new RuntimeException("no resultfile found"); } LOG.debug("parse results json"); ObjectMapper objectMapper = new ObjectMapper(); ModocotResultSummary resultSummary = objectMapper.readValue( resultFile.toURI().toURL(), ModocotResultSummary.class); // convert to moddle plugin readable format and return to moodle LOG.debug("convert to moodle understandable format"); LegacyMoodleResult moodleResult = LegacyMoodleResult.convertToModdleResult(resultSummary); LOG.info("check for provided Ticketsystem information"); UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary); LOG.info("submission tested successfully"); return moodleResult; } }