Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion backend/src/Configuration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AssignmentData } from "./database/Persister";
import { EnvironmentDescription } from "./Environment";

// Configuration.ts holds the main configuration regarding the assignments that can be deployed, its contents can also be red from MongoDB, which would make this file obsolete
Expand Down Expand Up @@ -1141,14 +1142,35 @@ environments.set("CC-Lab-1", {
assignmentLabSheet: "/home/p4/labs/lab1/README.md",
});

export default environments;
function getEnvironments(): Map<string, EnvironmentDescription> {
return new Map(
Array.from(environments.entries())
.map(([key, value]) => {
updateEnvironment(value);
return [key, value];
})
);
}

export function updateEnvironments(
updatedEnvironments: Map<string, EnvironmentDescription>,
): void {
environments.clear();

updatedEnvironments.forEach((value, key) => {
updateEnvironment(value)
environments.set(key, value);
});
}

export function updateEnvironment(env: EnvironmentDescription | AssignmentData): void {
const bonusPoints = env.steps?.reduce((sum, step) => sum + (step.bonusPoints ?? 0), 0) ?? 0;

if (env.maxBonusPoints !== undefined && bonusPoints > env.maxBonusPoints) {
env.maxBonusPoints = bonusPoints;
} else if (env.maxBonusPoints === undefined && bonusPoints > 0) {
env.maxBonusPoints = bonusPoints;
}
}

export default getEnvironments();
43 changes: 41 additions & 2 deletions backend/src/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export interface AssignmentStep {
name: string;
label: string;
tests: Array<AssignmentStepTestType>;
bonusPoints?: number;
}

interface GradualAssistanceType {
failedCounter: number;
hint: string;
}

interface AssignmentStepTestSSHCommand {
Expand All @@ -72,6 +78,7 @@ interface AssignmentStepTestSSHCommand {
stdOutMatch: string;
successMessage: string;
errorHint: string;
gradualAssistance?: Array<GradualAssistanceType>;
}

interface AssignmentStepTestTerminalBufferSearch {
Expand All @@ -80,6 +87,7 @@ interface AssignmentStepTestTerminalBufferSearch {
match: string;
successMessage: string;
errorHint: string;
gradualAssistance?: Array<GradualAssistanceType>;
}

type AssignmentStepTestType =
Expand Down Expand Up @@ -170,6 +178,23 @@ export default class Environment {
private filehandler!: FileHandler | undefined;
private groupNumber: number;
private sessionId: string;
private testCounter: Map<string, number> = new Map<string, number>();

private getErrorHint(test: AssignmentStepTestType, stepIndex: string) {
if (test.gradualAssistance === undefined) {
return test.errorHint;
}

const attempts = (this.testCounter.get(stepIndex) ?? 0) + 1;
this.testCounter.set(stepIndex, attempts);

const assistances = test.gradualAssistance.filter(a => a.failedCounter <= attempts).sort((a, b) => b.failedCounter - a.failedCounter);
if (assistances.length === 0) {
return test.errorHint;
}

return assistances[0].hint;
}

public static getActiveEnvironment(
environmentId: string,
Expand Down Expand Up @@ -1072,8 +1097,9 @@ export default class Environment {
}
}
}

if (testPassed !== true)
testOutput += "FAILED: " + test.errorHint + " ";
testOutput += "FAILED: " + this.getErrorHint(test, stepIndex);
}
} else if (test.type === "SSHCommand") {
await this.runSSHCommand(test.command, test.stdOutMatch)
Expand All @@ -1082,14 +1108,16 @@ export default class Environment {
testPassed = true;
})
.catch(() => {
testOutput += "FAILED: " + test.errorHint + " ";
testOutput += "FAILED: " + this.getErrorHint(test, stepIndex) + " ";
});
}

// if any of the terminalStates matched
if (testPassed === true && someTestsFailed !== true)
someTestsFailed = false;
else someTestsFailed = true;
}

if (someTestsFailed !== undefined && someTestsFailed === false)
return Promise.resolve({
code: 201,
Expand Down Expand Up @@ -1189,12 +1217,23 @@ export default class Environment {
}
}

let bonusPoints = 0;
if (this.configuration.steps !== undefined) {
for (let i = 0; i < parseInt(stepIndex); i++) {
const step = this.configuration.steps[i];
const testResult = await this.test(i.toString(), terminalStates);
if (testResult.code === 201)
bonusPoints += step.bonusPoints ?? 0;
}
}

await this.persister.SubmitUserEnvironment(
this.username,
this.groupNumber,
this.environmentId,
terminalStates,
submittedFiles,
bonusPoints
);
}

Expand Down
12 changes: 12 additions & 0 deletions backend/src/database/MemoryPersister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export default class MemoryPersister implements Persister {
environment: string,
terminalStates: TerminalStateType[],
submittedFiles: SubmissionFileType[],
bonusPoints: number
): Promise<void> {
return new Promise((resolve) => {
console.log(
Expand Down Expand Up @@ -171,6 +172,12 @@ export default class MemoryPersister implements Persister {
);
}

fs.writeFileSync(
path.resolve(resultPath, "bonusPoints.txt"),
bonusPoints.toString(),
"utf8"
);

resolve();
});
}
Expand Down Expand Up @@ -208,9 +215,14 @@ export default class MemoryPersister implements Persister {
assignmentName = submissionDir.substring(username.length + 1);
}

let bonusPoints = Number.parseInt(fs.readFileSync(path.resolve(submissionDir, "bonusPoints.txt"), {encoding: "utf8"}));
if (isNaN(bonusPoints))
bonusPoints = 0;

const submission = {
assignmentName: assignmentName,
lastChanged: lastMTime,
points: bonusPoints
};

submissions.push(submission);
Expand Down
18 changes: 14 additions & 4 deletions backend/src/database/MongoDBPersister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
SubmissionFileType,
TerminalStateType,
} from "../Environment";
import environments, { updateEnvironments } from "../Configuration";
import environments, { updateEnvironments, updateEnvironment } from "../Configuration";

const saltRounds = 10;

Expand Down Expand Up @@ -247,6 +247,7 @@ export default class MongoDBPersister implements Persister {
environment: string,
terminalStates: TerminalStateType[],
submittedFiles: SubmissionFileType[],
bonusPoints: number
): Promise<void> {
console.log(
"Storing assignment result for user: " +
Expand Down Expand Up @@ -308,6 +309,7 @@ export default class MongoDBPersister implements Persister {
submissionCreated: now,
terminalStatus: terminalStates,
submittedFiles: submittedFiles,
points: bonusPoints
})
.catch((err) => {
throw new Error("Failed to store submissions in mongodb.\n" + err);
Expand Down Expand Up @@ -547,8 +549,12 @@ export default class MongoDBPersister implements Persister {
return client
.db()
.collection<AssignmentData>("assignments")
.find({}, { projection: { _id: 1, name: 1, maxBonusPoints: 1 } })
.toArray();
.find({}, { projection: { _id: 1, name: 1, steps: 1, maxBonusPoints: 1 } })
.toArray()
.then(assignments => {
assignments.forEach(a => updateEnvironment(a));
return assignments;
});
}

async UpdateAssignementsForCourse(
Expand Down Expand Up @@ -588,7 +594,11 @@ export default class MongoDBPersister implements Persister {
{ $unwind: "$assignments" },
{ $replaceRoot: { newRoot: "$assignments" } },
])
.toArray();
.toArray()
.then(assignments => {
assignments.forEach(a => updateEnvironment(a));
return assignments;
});
}

async GetAllSubmissions(): Promise<SubmissionAdminOverviewEntry[]> {
Expand Down
3 changes: 3 additions & 0 deletions backend/src/database/Persister.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AssignmentStep,
Submission,
SubmissionAdminOverviewEntry,
SubmissionFileType,
Expand Down Expand Up @@ -62,6 +63,7 @@ export interface ResponseObject {
export interface AssignmentData {
_id: string;
name: string;
steps?: Array<AssignmentStep>;
maxBonusPoints?: number;
}

Expand Down Expand Up @@ -98,6 +100,7 @@ export interface Persister {
environment: string,
terminalStates: TerminalStateType[],
submittedFiles: SubmissionFileType[],
bonusPoints: number
) => Promise<void>;
GetUserSubmissions: (
username: string,
Expand Down
4 changes: 3 additions & 1 deletion backend/src/providers/DockerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,11 @@ export default class DockerProvider implements InstanceProvider {
ExposedPorts: exposedPorts,
HostConfig: {
PortBindings: portBindings,
Privileged: true,
//Privileged: true,
Privileged: false,
AutoRemove: true,
Binds: hostConfigBinds,
CapAdd: ["NET_ADMIN"]
},
Env: envs,
};
Expand Down
2 changes: 1 addition & 1 deletion backend/src/routes/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export default (persister: Persister, provider: InstanceProvider): Router => {
.json({ status: "error", message: "Environment not found" });
return;
}

Environment.createEnvironment(
reqWithUser.user.username,
reqWithUser.user.groupNumber,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/Theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function Theme(): JSX.Element {
<ThemeProvider theme={theme}>
<CssBaseline />
<SnackbarProvider
anchorOrigin={{ horizontal: "left", vertical: "bottom" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
Components={{
info: ColorSwitchingSnackbar,
success: ColorSwitchingSnackbar,
Expand Down