TaskUpload.java 8.42 KB
Newer Older
Dominik Vayhinger's avatar
Dominik Vayhinger committed
1
2
package de.hftstuttgart.rest.v1.task;

3
4
5
6
7
8
9
10
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;
Dominik Vayhinger's avatar
Dominik Vayhinger committed
11
import de.hftstuttgart.utils.JGitUtil;
12
import de.hftstuttgart.utils.UnifiedTicketingUtil;
Dominik Vayhinger's avatar
Dominik Vayhinger committed
13
14
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
15
import org.springframework.core.env.Environment;
Dominik Vayhinger's avatar
Dominik Vayhinger committed
16
17
18
19
20
21
22
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;
23
24
25
26
27
28
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;
Dominik Vayhinger's avatar
Dominik Vayhinger committed
29
30
31
32
33
34
35
36
37
38
39

/**
 * 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;
40
    private final DockerUtil dockerUtil;
Dominik Vayhinger's avatar
Dominik Vayhinger committed
41
    private final String assignmentBasePath;
42
43
    private final Path testTmpPathHost;
    private final Path testTmpPathModocot;
Dominik Vayhinger's avatar
Dominik Vayhinger committed
44
45


46
47
48
49
50
    public TaskUpload(
        Environment env,
        JGitUtil jGitUtil,
        DockerUtil dockerUtil
    ) {
Dominik Vayhinger's avatar
Dominik Vayhinger committed
51
        this.jGitUtil = jGitUtil;
52
        this.dockerUtil = dockerUtil;
Dominik Vayhinger's avatar
Dominik Vayhinger committed
53

54
55
56
57
58
        // 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();
Dominik Vayhinger's avatar
Dominik Vayhinger committed
59

60
61
62
63
        // 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"));
    }
Dominik Vayhinger's avatar
Dominik Vayhinger committed
64

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    @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");
            }
Dominik Vayhinger's avatar
Dominik Vayhinger committed
128
129
        }

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
        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");
            }
        }
Dominik Vayhinger's avatar
Dominik Vayhinger committed
160

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
        // 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");
Dominik Vayhinger's avatar
Dominik Vayhinger committed
193
194
        }

195
196
197
198
199
        LOG.debug("parse results json");
        ObjectMapper objectMapper = new ObjectMapper();
        ModocotResultSummary resultSummary = objectMapper.readValue(
            resultFile.toURI().toURL(),
            ModocotResultSummary.class);
Dominik Vayhinger's avatar
Dominik Vayhinger committed
200

201
202
203
204
        // convert to moddle plugin readable format and return to moodle
        LOG.debug("convert to moodle understandable format");
        LegacyMoodleResult moodleResult = LegacyMoodleResult.convertToModdleResult(resultSummary);

205
206
207
        LOG.info("check for provided Ticketsystem information");
        UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);

208
209
        LOG.info("submission tested successfully");
        return moodleResult;
Dominik Vayhinger's avatar
Dominik Vayhinger committed
210
211
    }
}