mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Adds container cleaning - doesn't clean populated container on reboot
This commit is contained in:
parent
3c2bcb4ed4
commit
806935e48c
4 changed files with 114 additions and 65 deletions
|
|
@ -6,7 +6,8 @@ const DockerRoomProvider = require('../roomsProviders/docker-provider.js');
|
|||
//const KubernetesRoomProvider = require('../roomsProviders/kubernetes-provider');
|
||||
|
||||
const NB_CODE_CHARS = 6;
|
||||
const DEFAULT_HOST = "172.18.0.5:4500" // must be room ip not name
|
||||
const NB_MS_UPDATE_ROOM = 1000;
|
||||
const NB_MS_CLEANUP = 30000;
|
||||
|
||||
class RoomsController {
|
||||
constructor(options = {}, roomRepository) {
|
||||
|
|
@ -16,7 +17,7 @@ class RoomsController {
|
|||
roomRepository
|
||||
);
|
||||
this.roomRepository = roomRepository;
|
||||
this.setupCleanup();
|
||||
this.setupTasks();
|
||||
}
|
||||
|
||||
createProvider(type, options, repository) {
|
||||
|
|
@ -37,10 +38,17 @@ class RoomsController {
|
|||
}
|
||||
}
|
||||
|
||||
setupCleanup() {
|
||||
async setupTasks(){
|
||||
await this.provider.syncInstantiatedRooms();
|
||||
// Update rooms
|
||||
setInterval(() => {
|
||||
this.provider.updateRoomsInfo().catch(console.error);
|
||||
}, NB_MS_UPDATE_ROOM);
|
||||
|
||||
// Cleanup rooms
|
||||
setInterval(() => {
|
||||
this.provider.cleanup().catch(console.error);
|
||||
}, 30000);
|
||||
}, NB_MS_CLEANUP);
|
||||
}
|
||||
|
||||
async createRoom(options = {}) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
class Room {
|
||||
constructor(id, name, host, nbStudents = 0) { // Default nbStudents to 0
|
||||
constructor(id, name, host, nbStudents = 0,) { // Default nbStudents to 0
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
|
||||
if (!host.startsWith('http://') && !host.startsWith('https://')) {
|
||||
host = 'http://' + host;
|
||||
}
|
||||
this.host = host;
|
||||
|
||||
this.nbStudents = nbStudents;
|
||||
this.mustBeCleaned = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,15 +53,21 @@ class RoomRepository {
|
|||
return await this.collection.find().toArray();
|
||||
}
|
||||
|
||||
async update(room) {
|
||||
async update(room,roomId = null) {
|
||||
await this.init();
|
||||
|
||||
const searchId = roomId ?? room.id;
|
||||
|
||||
const result = await this.collection.updateOne(
|
||||
{ id: room.id },
|
||||
{ $set: room }
|
||||
{ id: searchId },
|
||||
{ $set: room },
|
||||
{ upsert: false }
|
||||
);
|
||||
|
||||
|
||||
if (result.modifiedCount === 0) {
|
||||
console.warn(`Room with id ${room.id} was not updated because it was not found.`);
|
||||
if (result.matchedCount > 0) {
|
||||
return true; // Document exists but no changes needed
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@
|
|||
* @typedef {import('../../types/room').BaseProviderConfig} BaseProviderConfig
|
||||
*/
|
||||
|
||||
const MIN_NB_SECONDS_BEFORE_CLEANUP = process.env.MIN_NB_SECONDS_BEFORE_CLEANUP || 60
|
||||
|
||||
class BaseRoomProvider {
|
||||
constructor(config = {}, roomRepository) {
|
||||
this.config = config;
|
||||
this.roomRepository = roomRepository;
|
||||
|
||||
this.quiz_docker_image = process.env.QUIZROOM_IMAGE || "evaluetonsavoir-quizroom";
|
||||
}
|
||||
|
||||
async createRoom(roomId, options) {
|
||||
|
|
@ -31,19 +35,32 @@ class BaseRoomProvider {
|
|||
throw new Error("Method not implemented");
|
||||
}
|
||||
|
||||
async updateRoomInfo(roomId, info) {
|
||||
let room = await this.getRoomInfo(roomId);
|
||||
async syncInstantiatedRooms(){
|
||||
throw new Error("Method not implemented");
|
||||
}
|
||||
|
||||
if (!room) return false;
|
||||
async updateRoomsInfo() {
|
||||
const rooms = await this.roomRepository.getAll();
|
||||
for(var room of rooms){
|
||||
const url = `${room.host}/health`;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
room.mustBeCleaned = true;
|
||||
await this.roomRepository.update(room);
|
||||
continue;
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
room.nbStudents = json.connections;
|
||||
room.mustBeCleaned = room.nbStudents === 0 && json.uptime >MIN_NB_SECONDS_BEFORE_CLEANUP;
|
||||
|
||||
for (let key of Object.keys(room)) {
|
||||
if (info[key] !== undefined) {
|
||||
room[key] = info[key];
|
||||
await this.roomRepository.update(room);
|
||||
} catch (error) {
|
||||
console.error(`Error updating room ${room.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.roomRepository.update(room);
|
||||
return result != null;
|
||||
}
|
||||
|
||||
async getRoomInfo(roomId) {
|
||||
|
|
|
|||
|
|
@ -5,26 +5,53 @@ const BaseRoomProvider = require("./base-provider.js");
|
|||
class DockerRoomProvider extends BaseRoomProvider {
|
||||
constructor(config, roomRepository) {
|
||||
super(config, roomRepository);
|
||||
this.docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
||||
const dockerSocket = process.env.DOCKER_SOCKET || "/var/run/docker.sock";
|
||||
|
||||
this.docker = new Docker({ socketPath: dockerSocket });
|
||||
}
|
||||
|
||||
async syncInstantiatedRooms(){
|
||||
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_")) {
|
||||
console.warn(`Container ${container_name} does not follow the room naming convention, removing it.`);
|
||||
const curContainer = this.docker.getContainer(container.Id);
|
||||
await curContainer.stop();
|
||||
await curContainer.remove();
|
||||
containerIds.delete(container.Id);
|
||||
console.warn(`Container ${container_name} removed.`);
|
||||
}
|
||||
else{
|
||||
console.warn(`Found orphan container : ${container_name}`);
|
||||
const roomId = container_name.slice(5);
|
||||
const room = await this.roomRepository.get(roomId);
|
||||
|
||||
if (!room) {
|
||||
console.warn(`container not our rooms database`);
|
||||
const containerInfo = await this.docker.getContainer(container.Id).inspect();
|
||||
const containerIP = containerInfo.NetworkSettings.Networks.evaluetonsavoir_quiz_network.IPAddress;
|
||||
const host = `${containerIP}:4500`;
|
||||
console.warn(`Creating room ${roomId} in our database - host : ${host}`);
|
||||
return await this.roomRepository.create(new Room(roomId, container_name, host));
|
||||
}
|
||||
|
||||
console.warn(`room ${roomId} already in our database`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createRoom(roomId, options) {
|
||||
const container_name = `room_${roomId}`;
|
||||
|
||||
const containerConfig = {
|
||||
Image: 'evaluetonsavoir-quizroom',
|
||||
Image: this.quiz_docker_image,
|
||||
name: container_name,
|
||||
ExposedPorts: {
|
||||
"4500/tcp": {}
|
||||
},
|
||||
HostConfig: {
|
||||
PortBindings: {
|
||||
"4500/tcp": [
|
||||
{
|
||||
HostPort: ""
|
||||
}
|
||||
]
|
||||
},
|
||||
NetworkMode: "evaluetonsavoir_quiz_network"
|
||||
},
|
||||
Env: [...options.env || [], `ROOM_ID=${roomId}`]
|
||||
|
|
@ -36,13 +63,14 @@ class DockerRoomProvider extends BaseRoomProvider {
|
|||
const containerInfo = await container.inspect();
|
||||
const containerIP = containerInfo.NetworkSettings.Networks.evaluetonsavoir_quiz_network.IPAddress;
|
||||
|
||||
const host = `${containerIP}:4500`;
|
||||
const host = `${containerIP}:${this.quiz_docker_port ?? "4500"}`;
|
||||
return await this.roomRepository.create(new Room(roomId, container_name, host, 0));
|
||||
}
|
||||
|
||||
|
||||
async deleteRoom(roomId) {
|
||||
const container_name = `room_${roomId}`;
|
||||
await this.roomRepository.delete(roomId);
|
||||
|
||||
try {
|
||||
const container = this.docker.getContainer(container_name);
|
||||
|
|
@ -61,8 +89,7 @@ class DockerRoomProvider extends BaseRoomProvider {
|
|||
throw new Error("Failed to delete room");
|
||||
}
|
||||
}
|
||||
|
||||
await this.roomRepository.delete(roomId);
|
||||
|
||||
console.log(`Room ${roomId} deleted from repository.`);
|
||||
}
|
||||
|
||||
|
|
@ -117,44 +144,29 @@ class DockerRoomProvider extends BaseRoomProvider {
|
|||
|
||||
async cleanup() {
|
||||
const rooms = await this.roomRepository.getAll();
|
||||
const roomIds = new Set(rooms.map(room => room.id));
|
||||
|
||||
const containers = await this.docker.listContainers({ all: true });
|
||||
const containerIds = new Set();
|
||||
|
||||
for (const containerInfo of containers) {
|
||||
const containerName = containerInfo.Names[0].replace("/", "");
|
||||
if (containerName.startsWith("room_")) {
|
||||
const roomId = containerName.split("_")[1];
|
||||
containerIds.add(roomId);
|
||||
|
||||
if (!roomIds.has(roomId)) {
|
||||
try {
|
||||
const container = this.docker.getContainer(containerInfo.Id);
|
||||
await container.stop();
|
||||
await container.remove();
|
||||
console.log(`Loose container ${containerName} deleted.`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete loose container ${containerName}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const room of rooms) {
|
||||
if (!containerIds.has(room.id)) {
|
||||
for (let room of rooms) {
|
||||
if (room.mustBeCleaned) {
|
||||
try {
|
||||
await this.roomRepository.delete(room.id);
|
||||
console.log(`Orphan room ${room.id} deleted from repository.`);
|
||||
await this.deleteRoom(room.id);
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete orphan room ${room.id} from repository:`, error);
|
||||
console.error(`Error cleaning up room ${room.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Cleanup of loose containers and orphan rooms completed.");
|
||||
}
|
||||
let containers = await this.docker.listContainers();
|
||||
containers = containers.filter(container => container.Image === this.quiz_docker_image);
|
||||
const roomIds = rooms.map(room => room.id);
|
||||
|
||||
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();
|
||||
console.warn(`Orphan container ${container.Names[0]} removed.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DockerRoomProvider;
|
||||
|
|
|
|||
Loading…
Reference in a new issue