347 lines
11 KiB
JavaScript
347 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)
|
|
var res = []
|
|
if (output_dir.length != 0)
|
|
{
|
|
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)
|
|
}
|
|
})
|
|
})
|
|
if (res.length === 0) {
|
|
socket.emit('error', 'Please check my_model.py')
|
|
}
|
|
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)
|
|
})
|