com2014-server/server.js

344 lines
11 KiB
JavaScript

const fs = require('fs')
const { v4: uuidv4 } = require('uuid');
const MAX_LEADERBOARD = 20
// Web Server
var express = require('express')
var app = express()
var router = express.Router()
// Upload Files
var upload = require('./app/config/multer.config.js')
// Static Website
app.use(express.static('resources'))
var http = require('http').createServer(app)
var io = require('socket.io')(http)
// Docker Container
const {Docker} = require('node-docker-api')
const docker = new Docker({ socketPath: '/var/run/docker.sock' })
// Console Log
const stripAnsi = require('strip-ansi')
// Set up directories
global.__basedir = __dirname
const path = require('path')
current_path = process.cwd()
// Initialize Leaderboard
global.leaderboard = {}
leaderboard.ulysses16 = []
leaderboard.att48 = []
leaderboard.st70 = []
leaderboard.a280 = []
leaderboard.pcb442 = []
leaderboard.dsj1000 = []
var write_leaderboard = () => {
fs.writeFile('leaderboard.json', JSON.stringify(leaderboard), (err) => {
if (err) throw err
console.log('Data written to file')
})
}
// Read from JSON file first if exists
try {
if (fs.existsSync('leaderboard.json')) {
//file exists
console.log("Existed leaderboard")
leaderboard = require('./leaderboard.json')
console.log(leaderboard)
}
else {
write_leaderboard()
}
}
catch(err) {
console.error(err)
}
var update_leaderboard = (obj) => {
// Update each TSP entry
if(obj.name === 'ulysses16') {
leaderboard.ulysses16.push(obj.data)
}
else if (obj.name === 'att48') {
leaderboard.att48.push(obj.data)
}
else if (obj.name === 'st70') {
leaderboard.st70.push(obj.data)
}
else if (obj.name === 'a280') {
leaderboard.a280.push(obj.data)
}
else if (obj.name === 'pcb442') {
leaderboard.pcb442.push(obj.data)
}
else if (obj.name === 'dsj1000') {
leaderboard.dsj1000.push(obj.data)
}
// Only accept top MAX_LEADERBOARD results
leaderboard.ulysses16.sort(function(a, b){return a.fitness - b.fitness})
leaderboard.ulysses16 = leaderboard.ulysses16.slice(0, MAX_LEADERBOARD)
leaderboard.att48.sort(function(a, b){return a.fitness - b.fitness}).slice(0, MAX_LEADERBOARD)
leaderboard.att48 = leaderboard.att48.slice(0, MAX_LEADERBOARD)
leaderboard.st70.sort(function(a, b){return a.fitness - b.fitness}).slice(0, MAX_LEADERBOARD)
leaderboard.st70 = leaderboard.st70.slice(0, MAX_LEADERBOARD)
leaderboard.a280.sort(function(a, b){return a.fitness - b.fitness})
leaderboard.a280 = leaderboard.a280.slice(0, MAX_LEADERBOARD)
leaderboard.pcb442.sort(function(a, b){return a.fitness - b.fitness})
leaderboard.pcb442 = leaderboard.pcb442.slice(0, MAX_LEADERBOARD)
leaderboard.dsj1000.sort(function(a, b){return a.fitness - b.fitness})
leaderboard.dsj1000 = leaderboard.dsj1000.slice(0, MAX_LEADERBOARD)
// Write to json file
write_leaderboard()
}
// Get submission results
var update_submission = (socket, sessionID) => {
var output_dir = current_path.toString() + '/output/' + sessionID + '/'
console.log(output_dir)
if (output_dir.length != 0)
{
var res = []
socket.emit('info', 'Got output')
// Read output dir
fs.readdir(output_dir, (err, files) => {
if (err) {
console.log(err)
return
}
// Read each result in txt file
files.forEach(file => {
if (path.extname(file) === '.txt') {
var i = 0
// Result object
var obj = {}
obj.name = file
try {
// read contents of the file
const data = fs.readFileSync(output_dir + file, 'UTF-8')
// split the contents by new line
const lines = data.split(/\r?\n/)
lines.forEach((line) => {
i = i + 1
// The first line is the fitness
if(i == 1) {
var fitness = parseFloat(line)
if(fitness < 0) {
obj.fitness = parseInt(line)
}
else {
obj.fitness = fitness
}
}
// The second line is the path
else if (i == 2) {
// Only read when there is no error
if(obj.fitness > 0) {
solution_array = line.split(",")
for(var j = 0; j < solution_array.length; j++) {
solution_array[i] = parseInt(solution_array[i], 10)
}
obj.solution = solution_array
}
else {
obj.solution = []
}
}
})
} catch (err) {
console.error(err)
}
res.push(obj)
}
})
socket.emit('result', JSON.stringify(res))
})
}
else {
console.log('no result')
socket.emit('error', 'Please check my_model.py')
}
}
var check_time = 0
var docker_build = (socket, sessionID) => {
check_time = 0
console.log('[server][start]', sessionID)
socket.emit('start')
fs.mkdir(current_path + '/output/' + sessionID.toString(), () => {
socket.emit('info', 'Output directort created.')
socket.emit('info', 'Test started, this may take a while.')
})
docker.container.create({
Image: 'com2014-tsp',
HostConfig: {
Binds: [
'/uploads/' + sessionID.toString() + '/my_model.py:/tsp/model/my_model.py',
"/output/" + sessionID.toString() + "/:/output/"
]
}
})
.then( (container) => {
// Start container
container_id = container.data.Id
socket.emit('info','Container Id: ' + container_id)
return container.start()
})
.then(container => container.logs({
follow: true,
stdout: true,
stderr: true
}))
.then(stream => {
// Write container logs to client
stream.on('data', (info) => {
console.log(stripAnsi(new Buffer.from(info).toString()))
socket.emit('info', stripAnsi(new Buffer.from(info).toString('ascii')))
}),
stream.on('error', (err) => {
console.log(err)
socket.emit('info', stripAnsi(new Buffer.from(err).toString('ascii')))
})
})
.catch(error => console.log(error))
// Check if container is still running
var intervalObj
// Timeout (each container runs only for 60s at most)
var timeoutObj
var finished = false
// Check if container is still running
intervalObj = setInterval(() => {
if(!finished)
{
docker.container.list({all:true})
.then((containers) => {
containers.forEach(container => {
if(container.data.Id == container_id) {
if(check_time % 30 == 0) {
socket.emit('info', 'Container Status: ' + container.data.State)
}
if(container.data.State === 'exited')
{
// Finished Running
finished = true
clearInterval(intervalObj)
clearTimeout(timeoutObj)
socket.emit('info', 'Container exited')
// Delete container
container.delete({ force: true })
// Get submission results
update_submission(socket, sessionID)
}
}
})
})
.catch(error => console.log(error))
check_time = check_time + 1
}
}, 2000)
// Timeout (each container runs only for 60s at most)
timeoutObj = setTimeout(() => {
console.log('end')
if(!finished)
{
docker.container.list({all:true})
.then((containers) => {
containers.forEach(container => {
if(container.data.Id == container_id) {
socket.emit('info', 'Timeout, cleaning ' + container.data.State + ' container')
// Delete container
container.delete({ force: true })
// Finished Running
finished = true
clearInterval(intervalObj)
clearTimeout(timeoutObj)
clearInterval(intervalObj)
socket.emit('info', 'Container removed')
}
})
})
.catch(error => console.log(error))
}
}, 900000)
}
var clients = 0
io.on('connection', (socket) => {
// New client connected
const sessionID = socket.id
console.log('[client][connection]', sessionID)
// Update client number
clients = clients + 1
io.sockets.emit('users_count', clients)
// Inform new client the leaderboard
// console.log(leaderboard)
socket.emit('leaderboard', leaderboard)
// Client disconnected
socket.on('disconnect', () => {
clients = clients - 1
console.log('[client][disconnect]', sessionID)
io.sockets.emit('users_count', clients)
})
// Client request a build
socket.on('build', () => {
console.log('[client][build]', sessionID)
docker_build(socket, sessionID)
})
// Client submited a new result
socket.on('submit', (obj) => {
console.log('[client][submit]', sessionID)
obj.data.id = uuidv4()
update_leaderboard(obj)
io.emit('leaderboard', leaderboard)
})
})
require('./app/routers/file.router.js')(app, router, upload)
// Create a Server
var server = http.listen(8080, () => {
var host = server.address().address
var port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})