2024-11-10 15:42:46 -05:00
|
|
|
const Docker = require("dockerode");
|
2024-11-12 11:44:15 -05:00
|
|
|
const { Room } = require("../models/room.js");
|
2024-11-10 15:42:46 -05:00
|
|
|
const BaseRoomProvider = require("./base-provider.js");
|
|
|
|
|
|
|
|
|
|
class DockerRoomProvider extends BaseRoomProvider {
|
|
|
|
|
constructor(config, roomRepository) {
|
|
|
|
|
super(config, roomRepository);
|
2024-11-12 01:22:54 -05:00
|
|
|
const dockerSocket = process.env.DOCKER_SOCKET || "/var/run/docker.sock";
|
2024-11-12 02:33:19 -05:00
|
|
|
|
2024-11-12 01:22:54 -05:00
|
|
|
this.docker = new Docker({ socketPath: dockerSocket });
|
2024-11-12 02:33:19 -05:00
|
|
|
this.docker_network = 'evaluetonsavoir_quiz_network';
|
2024-11-12 01:22:54 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-12 02:33:19 -05:00
|
|
|
async syncInstantiatedRooms() {
|
2024-11-12 01:22:54 -05:00
|
|
|
let containers = await this.docker.listContainers();
|
|
|
|
|
containers = containers.filter(container => container.Image === this.quiz_docker_image);
|
|
|
|
|
|
|
|
|
|
const containerIds = new Set(containers.map(container => container.Id));
|
|
|
|
|
|
|
|
|
|
for (let container of containers) {
|
|
|
|
|
const container_name = container.Names[0].slice(1);
|
|
|
|
|
if (!container_name.startsWith("room_")) {
|
2024-11-12 02:33:19 -05:00
|
|
|
console.warn(`Le conteneur ${container_name} ne suit pas la convention de nommage, il sera supprimé.`);
|
2024-11-12 01:22:54 -05:00
|
|
|
const curContainer = this.docker.getContainer(container.Id);
|
|
|
|
|
await curContainer.stop();
|
|
|
|
|
await curContainer.remove();
|
|
|
|
|
containerIds.delete(container.Id);
|
2024-11-12 02:33:19 -05:00
|
|
|
console.warn(`Le conteneur ${container_name} a été supprimé.`);
|
2024-11-12 01:22:54 -05:00
|
|
|
}
|
2024-11-12 02:33:19 -05:00
|
|
|
else {
|
|
|
|
|
console.warn(`Conteneur orphelin trouvé : ${container_name}`);
|
2024-11-12 01:22:54 -05:00
|
|
|
const roomId = container_name.slice(5);
|
|
|
|
|
const room = await this.roomRepository.get(roomId);
|
2024-11-12 02:33:19 -05:00
|
|
|
|
2024-11-12 01:22:54 -05:00
|
|
|
if (!room) {
|
2024-11-12 02:33:19 -05:00
|
|
|
console.warn(`Le conteneur n'est pas dans notre base de données.`);
|
2024-11-12 01:22:54 -05:00
|
|
|
const containerInfo = await this.docker.getContainer(container.Id).inspect();
|
|
|
|
|
const containerIP = containerInfo.NetworkSettings.Networks.evaluetonsavoir_quiz_network.IPAddress;
|
|
|
|
|
const host = `${containerIP}:4500`;
|
2024-11-12 02:33:19 -05:00
|
|
|
console.warn(`Création de la salle ${roomId} dans notre base de donnée - hôte : ${host}`);
|
2024-11-12 01:22:54 -05:00
|
|
|
return await this.roomRepository.create(new Room(roomId, container_name, host));
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-12 02:33:19 -05:00
|
|
|
console.warn(`La salle ${roomId} est déjà dans notre base de données.`);
|
2024-11-12 01:22:54 -05:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-10 15:42:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async createRoom(roomId, options) {
|
2024-11-10 23:41:03 -05:00
|
|
|
const container_name = `room_${roomId}`;
|
|
|
|
|
|
2024-11-12 02:33:19 -05:00
|
|
|
try {
|
|
|
|
|
const containerConfig = {
|
|
|
|
|
Image: this.quiz_docker_image,
|
|
|
|
|
name: container_name,
|
|
|
|
|
HostConfig: {
|
|
|
|
|
NetworkMode: this.docker_network,
|
|
|
|
|
RestartPolicy: {
|
|
|
|
|
Name: 'unless-stopped'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Env: [
|
|
|
|
|
`ROOM_ID=${roomId}`,
|
|
|
|
|
`PORT=${this.quiz_docker_port}`,
|
|
|
|
|
...(options.env || [])
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this.quiz_expose_port) {
|
|
|
|
|
containerConfig.ExposedPorts = {
|
|
|
|
|
[`${this.quiz_docker_port}/tcp`]: {}
|
|
|
|
|
};
|
|
|
|
|
containerConfig.HostConfig.PortBindings = {
|
|
|
|
|
[`${this.quiz_docker_port}/tcp`]: [{ HostPort: '' }] // Empty string for random port
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const container = await this.docker.createContainer(containerConfig);
|
|
|
|
|
await container.start();
|
|
|
|
|
|
|
|
|
|
const containerInfo = await container.inspect();
|
|
|
|
|
const networkInfo = containerInfo.NetworkSettings.Networks[this.docker_network];
|
|
|
|
|
|
|
|
|
|
if (!networkInfo) {
|
|
|
|
|
throw new Error(`Le conteneur n'as pu se connecter au réseau: ${this.docker_network}`);
|
|
|
|
|
}
|
2024-11-10 23:41:03 -05:00
|
|
|
|
2024-11-12 02:33:19 -05:00
|
|
|
const containerIP = networkInfo.IPAddress;
|
|
|
|
|
const host = `http://${containerIP}:${this.quiz_docker_port}`;
|
2024-11-10 23:41:03 -05:00
|
|
|
|
|
|
|
|
|
2024-11-12 02:33:19 -05:00
|
|
|
let health = false;
|
|
|
|
|
let attempts = 0;
|
|
|
|
|
const maxAttempts = 15;
|
|
|
|
|
|
|
|
|
|
while (!health && attempts < maxAttempts) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${host}/health`, {
|
|
|
|
|
timeout: 1000
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
health = true;
|
|
|
|
|
console.log(`Le conteneur ${container_name} est tombé actif en ${attempts + 1} tentatives`);
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(`Health check failed with status ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
attempts++;
|
|
|
|
|
console.log(`Attente du conteneur: ${container_name} (tentative ${attempts}/${maxAttempts})`);
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!health) {
|
|
|
|
|
console.error(`Container ${container_name} failed health check after ${maxAttempts} attempts`);
|
|
|
|
|
await container.stop();
|
|
|
|
|
await container.remove();
|
|
|
|
|
throw new Error(`Room ${roomId} did not respond within acceptable timeout`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await this.roomRepository.create(new Room(roomId, container_name, host, 0));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Échec de la création de la salle ${roomId}:`, error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
2024-11-10 15:42:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async deleteRoom(roomId) {
|
2024-11-11 11:32:46 -05:00
|
|
|
const container_name = `room_${roomId}`;
|
2024-11-12 01:22:54 -05:00
|
|
|
await this.roomRepository.delete(roomId);
|
2024-11-11 11:32:46 -05:00
|
|
|
|
2024-11-10 15:42:46 -05:00
|
|
|
try {
|
2024-11-11 11:32:46 -05:00
|
|
|
const container = this.docker.getContainer(container_name);
|
|
|
|
|
const containerInfo = await container.inspect();
|
2024-11-10 15:42:46 -05:00
|
|
|
|
2024-11-11 11:32:46 -05:00
|
|
|
if (containerInfo) {
|
|
|
|
|
await container.stop();
|
|
|
|
|
await container.remove();
|
2024-11-12 02:33:19 -05:00
|
|
|
console.log(`Le conteneur pour la salle ${roomId} a été arrêté et supprimé.`);
|
2024-11-11 11:32:46 -05:00
|
|
|
}
|
2024-11-10 15:42:46 -05:00
|
|
|
} catch (error) {
|
2024-11-11 11:32:46 -05:00
|
|
|
if (error.statusCode === 404) {
|
2024-11-12 02:33:19 -05:00
|
|
|
console.warn(`Le conteneur pour la salle ${roomId} n'as pas été trouvé, la salle sera supprimée de la base de données.`);
|
2024-11-11 11:32:46 -05:00
|
|
|
} else {
|
2024-11-12 02:33:19 -05:00
|
|
|
console.error(`Erreur pour la salle ${roomId}:`, error);
|
|
|
|
|
throw new Error("La salle :${roomId} n'as pas pu être supprimée.");
|
2024-11-11 11:32:46 -05:00
|
|
|
}
|
2024-11-10 15:42:46 -05:00
|
|
|
}
|
2024-11-12 02:33:19 -05:00
|
|
|
|
|
|
|
|
console.log(`La salle ${roomId} a été supprimée.`);
|
2024-11-10 15:42:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getRoomStatus(roomId) {
|
2024-11-10 16:33:45 -05:00
|
|
|
const room = await this.roomRepository.get(roomId);
|
2024-11-10 15:42:46 -05:00
|
|
|
if (!room) return null;
|
|
|
|
|
|
|
|
|
|
try {
|
2024-11-11 11:32:46 -05:00
|
|
|
const container = this.docker.getContainer(room.containerId || `room_${roomId}`);
|
2024-11-10 15:42:46 -05:00
|
|
|
const info = await container.inspect();
|
|
|
|
|
|
|
|
|
|
const updatedRoomInfo = {
|
|
|
|
|
...room,
|
|
|
|
|
status: info.State.Running ? "running" : "terminated",
|
|
|
|
|
containerStatus: {
|
|
|
|
|
Running: info.State.Running,
|
|
|
|
|
StartedAt: info.State.StartedAt,
|
|
|
|
|
FinishedAt: info.State.FinishedAt,
|
|
|
|
|
},
|
|
|
|
|
lastUpdate: Date.now(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await this.roomRepository.update(updatedRoomInfo);
|
|
|
|
|
return updatedRoomInfo;
|
|
|
|
|
} catch (error) {
|
2024-11-11 11:32:46 -05:00
|
|
|
if (error.statusCode === 404) {
|
2024-11-12 02:33:19 -05:00
|
|
|
console.warn(`Le conteneur pour la salle ${roomId} n'as pas été trouvé, il sera mis en état "terminé".`);
|
2024-11-11 11:32:46 -05:00
|
|
|
const terminatedRoomInfo = {
|
|
|
|
|
...room,
|
|
|
|
|
status: "terminated",
|
|
|
|
|
containerStatus: {
|
|
|
|
|
Running: false,
|
|
|
|
|
StartedAt: room.containerStatus?.StartedAt || null,
|
|
|
|
|
FinishedAt: Date.now(),
|
|
|
|
|
},
|
|
|
|
|
lastUpdate: Date.now(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await this.roomRepository.update(terminatedRoomInfo);
|
|
|
|
|
return terminatedRoomInfo;
|
|
|
|
|
} else {
|
2024-11-12 02:33:19 -05:00
|
|
|
console.error(`Une érreur s'est produite lors de l'obtention de l'état de la salle ${roomId}:`, error);
|
2024-11-11 11:32:46 -05:00
|
|
|
return null;
|
|
|
|
|
}
|
2024-11-10 15:42:46 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async listRooms() {
|
|
|
|
|
const rooms = await this.roomRepository.getAll();
|
|
|
|
|
return rooms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async cleanup() {
|
2024-11-11 11:32:46 -05:00
|
|
|
const rooms = await this.roomRepository.getAll();
|
2024-11-12 01:22:54 -05:00
|
|
|
for (let room of rooms) {
|
|
|
|
|
if (room.mustBeCleaned) {
|
2024-11-11 11:32:46 -05:00
|
|
|
try {
|
2024-11-12 01:22:54 -05:00
|
|
|
await this.deleteRoom(room.id);
|
2024-11-11 11:32:46 -05:00
|
|
|
} catch (error) {
|
2024-11-12 02:33:19 -05:00
|
|
|
console.error(`Érreur lors du néttoyage de la salle ${room.id}:`, error);
|
2024-11-11 11:32:46 -05:00
|
|
|
}
|
2024-11-10 15:42:46 -05:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-11 11:32:46 -05:00
|
|
|
|
2024-11-12 01:22:54 -05:00
|
|
|
let containers = await this.docker.listContainers();
|
|
|
|
|
containers = containers.filter(container => container.Image === this.quiz_docker_image);
|
|
|
|
|
const roomIds = rooms.map(room => room.id);
|
2024-11-11 11:32:46 -05:00
|
|
|
|
2024-11-12 01:22:54 -05:00
|
|
|
for (let container of containers) {
|
|
|
|
|
if (!roomIds.includes(container.Names[0].slice(6))) {
|
|
|
|
|
const curContainer = this.docker.getContainer(container.Id);
|
|
|
|
|
await curContainer.stop();
|
|
|
|
|
await curContainer.remove();
|
2024-11-12 02:33:19 -05:00
|
|
|
console.warn(`Conteneur orphelin ${container.Names[0]} supprimé.`);
|
2024-11-12 01:22:54 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-10 15:42:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = DockerRoomProvider;
|