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 KubernetesRoomProvider = require('../roomsProviders/kubernetes-provider');
|
||||||
|
|
||||||
const NB_CODE_CHARS = 6;
|
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 {
|
class RoomsController {
|
||||||
constructor(options = {}, roomRepository) {
|
constructor(options = {}, roomRepository) {
|
||||||
|
|
@ -16,7 +17,7 @@ class RoomsController {
|
||||||
roomRepository
|
roomRepository
|
||||||
);
|
);
|
||||||
this.roomRepository = roomRepository;
|
this.roomRepository = roomRepository;
|
||||||
this.setupCleanup();
|
this.setupTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
createProvider(type, options, repository) {
|
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(() => {
|
setInterval(() => {
|
||||||
this.provider.cleanup().catch(console.error);
|
this.provider.cleanup().catch(console.error);
|
||||||
}, 30000);
|
}, NB_MS_CLEANUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createRoom(options = {}) {
|
async createRoom(options = {}) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
class Room {
|
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.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
|
if (!host.startsWith('http://') && !host.startsWith('https://')) {
|
||||||
|
host = 'http://' + host;
|
||||||
|
}
|
||||||
this.host = host;
|
this.host = host;
|
||||||
|
|
||||||
this.nbStudents = nbStudents;
|
this.nbStudents = nbStudents;
|
||||||
|
this.mustBeCleaned = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,15 +53,21 @@ class RoomRepository {
|
||||||
return await this.collection.find().toArray();
|
return await this.collection.find().toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(room) {
|
async update(room,roomId = null) {
|
||||||
await this.init();
|
await this.init();
|
||||||
|
|
||||||
|
const searchId = roomId ?? room.id;
|
||||||
|
|
||||||
const result = await this.collection.updateOne(
|
const result = await this.collection.updateOne(
|
||||||
{ id: room.id },
|
{ id: searchId },
|
||||||
{ $set: room }
|
{ $set: room },
|
||||||
|
{ upsert: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.modifiedCount === 0) {
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,14 @@
|
||||||
* @typedef {import('../../types/room').BaseProviderConfig} BaseProviderConfig
|
* @typedef {import('../../types/room').BaseProviderConfig} BaseProviderConfig
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const MIN_NB_SECONDS_BEFORE_CLEANUP = process.env.MIN_NB_SECONDS_BEFORE_CLEANUP || 60
|
||||||
|
|
||||||
class BaseRoomProvider {
|
class BaseRoomProvider {
|
||||||
constructor(config = {}, roomRepository) {
|
constructor(config = {}, roomRepository) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.roomRepository = roomRepository;
|
this.roomRepository = roomRepository;
|
||||||
|
|
||||||
|
this.quiz_docker_image = process.env.QUIZROOM_IMAGE || "evaluetonsavoir-quizroom";
|
||||||
}
|
}
|
||||||
|
|
||||||
async createRoom(roomId, options) {
|
async createRoom(roomId, options) {
|
||||||
|
|
@ -31,19 +35,32 @@ class BaseRoomProvider {
|
||||||
throw new Error("Method not implemented");
|
throw new Error("Method not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateRoomInfo(roomId, info) {
|
async syncInstantiatedRooms(){
|
||||||
let room = await this.getRoomInfo(roomId);
|
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)) {
|
await this.roomRepository.update(room);
|
||||||
if (info[key] !== undefined) {
|
} catch (error) {
|
||||||
room[key] = info[key];
|
console.error(`Error updating room ${room.id}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.roomRepository.update(room);
|
|
||||||
return result != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRoomInfo(roomId) {
|
async getRoomInfo(roomId) {
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,53 @@ const BaseRoomProvider = require("./base-provider.js");
|
||||||
class DockerRoomProvider extends BaseRoomProvider {
|
class DockerRoomProvider extends BaseRoomProvider {
|
||||||
constructor(config, roomRepository) {
|
constructor(config, roomRepository) {
|
||||||
super(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) {
|
async createRoom(roomId, options) {
|
||||||
const container_name = `room_${roomId}`;
|
const container_name = `room_${roomId}`;
|
||||||
|
|
||||||
const containerConfig = {
|
const containerConfig = {
|
||||||
Image: 'evaluetonsavoir-quizroom',
|
Image: this.quiz_docker_image,
|
||||||
name: container_name,
|
name: container_name,
|
||||||
ExposedPorts: {
|
|
||||||
"4500/tcp": {}
|
|
||||||
},
|
|
||||||
HostConfig: {
|
HostConfig: {
|
||||||
PortBindings: {
|
|
||||||
"4500/tcp": [
|
|
||||||
{
|
|
||||||
HostPort: ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
NetworkMode: "evaluetonsavoir_quiz_network"
|
NetworkMode: "evaluetonsavoir_quiz_network"
|
||||||
},
|
},
|
||||||
Env: [...options.env || [], `ROOM_ID=${roomId}`]
|
Env: [...options.env || [], `ROOM_ID=${roomId}`]
|
||||||
|
|
@ -36,13 +63,14 @@ class DockerRoomProvider extends BaseRoomProvider {
|
||||||
const containerInfo = await container.inspect();
|
const containerInfo = await container.inspect();
|
||||||
const containerIP = containerInfo.NetworkSettings.Networks.evaluetonsavoir_quiz_network.IPAddress;
|
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));
|
return await this.roomRepository.create(new Room(roomId, container_name, host, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async deleteRoom(roomId) {
|
async deleteRoom(roomId) {
|
||||||
const container_name = `room_${roomId}`;
|
const container_name = `room_${roomId}`;
|
||||||
|
await this.roomRepository.delete(roomId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const container = this.docker.getContainer(container_name);
|
const container = this.docker.getContainer(container_name);
|
||||||
|
|
@ -61,8 +89,7 @@ class DockerRoomProvider extends BaseRoomProvider {
|
||||||
throw new Error("Failed to delete room");
|
throw new Error("Failed to delete room");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.roomRepository.delete(roomId);
|
|
||||||
console.log(`Room ${roomId} deleted from repository.`);
|
console.log(`Room ${roomId} deleted from repository.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,44 +144,29 @@ class DockerRoomProvider extends BaseRoomProvider {
|
||||||
|
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
const rooms = await this.roomRepository.getAll();
|
const rooms = await this.roomRepository.getAll();
|
||||||
const roomIds = new Set(rooms.map(room => room.id));
|
for (let room of rooms) {
|
||||||
|
if (room.mustBeCleaned) {
|
||||||
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)) {
|
|
||||||
try {
|
try {
|
||||||
await this.roomRepository.delete(room.id);
|
await this.deleteRoom(room.id);
|
||||||
console.log(`Orphan room ${room.id} deleted from repository.`);
|
|
||||||
} catch (error) {
|
} 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;
|
module.exports = DockerRoomProvider;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue