Merge pull request #25 from openHPI/webpython-hybrid
Fixed container destruction
This commit is contained in:
@ -25,8 +25,9 @@ In order to execute code submissions using Docker, source code files are written
|
||||
|
||||
- create *config/sendmail.yml*
|
||||
- create *config/smtp.yml*
|
||||
- use boot2docker if there is no native support for docker on your OS
|
||||
- use boot2docker or vagrant if there is no native support for docker on your OS
|
||||
- create seed data by executing `rake db:seed`
|
||||
- if you already created a configuration for your local installation and want to use vagrant, too, be sure to log into the vagrant instance via ssh and add your database user manually to the database. Afterwards, create, migrate and seed.
|
||||
|
||||
## Production Setup
|
||||
|
||||
@ -46,8 +47,8 @@ The application is compatible with MRI and JRuby. Due to superior parallelism, w
|
||||
|
||||
1.1
|
||||
|
||||
[ ] WebSocket Suppport
|
||||
[ ] Interactive Exercises
|
||||
[x] WebSocket Suppport
|
||||
[x] Interactive Exercises
|
||||
[ ] Allow Disabling of File Creation
|
||||
[ ] Set Container Recyling per Environment
|
||||
|
||||
|
@ -13,6 +13,9 @@ $(function() {
|
||||
var THEME = 'ace/theme/textmate';
|
||||
var REMEMBER_TAB = false;
|
||||
var AUTOSAVE_INTERVAL = 15 * 1000;
|
||||
var NONE = 0;
|
||||
var WEBSOCKET = 1;
|
||||
var SERVER_SEND_EVENT = 2;
|
||||
|
||||
var editors = [];
|
||||
var active_file = undefined;
|
||||
@ -20,6 +23,7 @@ $(function() {
|
||||
var running = false;
|
||||
var qa_api = undefined;
|
||||
var output_mode_is_streaming = true;
|
||||
var runmode = NONE;
|
||||
|
||||
var websocket,
|
||||
turtlescreen,
|
||||
@ -873,6 +877,7 @@ $(function() {
|
||||
var runCode = function(event) {
|
||||
event.preventDefault();
|
||||
if ($('#run').is(':visible')) {
|
||||
runmode = WEBSOCKET;
|
||||
createSubmission(this, null, function(response) {
|
||||
$('#stop').data('url', response.stop_url);
|
||||
running = true;
|
||||
@ -910,6 +915,7 @@ $(function() {
|
||||
|
||||
var scoreCode = function(event) {
|
||||
event.preventDefault();
|
||||
runmode = SERVER_SEND_EVENT;
|
||||
createSubmission(this, null, function(response) {
|
||||
showSpinner($('#assess'));
|
||||
var url = response.score_url;
|
||||
@ -1018,8 +1024,28 @@ $(function() {
|
||||
var stopCode = function(event) {
|
||||
event.preventDefault();
|
||||
if ($('#stop').is(':visible')) {
|
||||
if(runmode == WEBSOCKET){
|
||||
killWebsocketAndContainer();
|
||||
} else if (runmode == SERVER_SEND_EVENT) {
|
||||
stopCodeServerSendEvent(event);
|
||||
}
|
||||
runmode = NONE;
|
||||
}
|
||||
};
|
||||
|
||||
var stopCodeServerSendEvent = function(event){
|
||||
var jqxhr = ajax({
|
||||
data: {
|
||||
container_id: $('#stop').data('container').id
|
||||
},
|
||||
url: $('#stop').data('url')
|
||||
});
|
||||
jqxhr.always(function() {
|
||||
hideSpinner();
|
||||
running = false;
|
||||
toggleButtonStates();
|
||||
});
|
||||
jqxhr.fail(ajaxError);
|
||||
};
|
||||
|
||||
var killWebsocketAndContainer = function() {
|
||||
|
@ -112,8 +112,8 @@ class SubmissionsController < ApplicationController
|
||||
begin
|
||||
parsed = JSON.parse(data)
|
||||
if parsed['cmd'] == 'exit'
|
||||
Rails.logger.info("Client killed container.")
|
||||
@docker_client.kill_container(result[:container])
|
||||
Rails.logger.info("Client exited container.")
|
||||
@docker_client.exit_container(result[:container])
|
||||
else
|
||||
socket.send data
|
||||
end
|
||||
|
@ -39,10 +39,10 @@
|
||||
#output-col1
|
||||
// todo set to full width if turtle isnt used
|
||||
#prompt.input-group.hidden
|
||||
span.input-group-addon = 'Your input'
|
||||
span.input-group-addon = t('exercises.editor.input')
|
||||
input#prompt-input.form-control type='text'
|
||||
span.input-group-btn
|
||||
button#prompt-submit.btn.btn-primary type="button" = 'Send'
|
||||
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
||||
#output
|
||||
pre = t('.no_output_yet')
|
||||
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled]
|
||||
|
@ -176,6 +176,7 @@ de:
|
||||
destroy_file: Datei löschen
|
||||
download: Herunterladen
|
||||
dummy: Keine Aktion
|
||||
input: Ihre Eingabe
|
||||
lastsaved: 'Zuletzt gespeichert: '
|
||||
network: 'Während Ihr Code läuft, ist Port %{port} unter folgender Adresse erreichbar: <a href="%{address}" target="_blank">%{address}</a>.'
|
||||
render: Anzeigen
|
||||
@ -185,6 +186,7 @@ de:
|
||||
requestComments: Kommentare erbitten
|
||||
save: Speichern
|
||||
score: Bewerten
|
||||
send: Senden
|
||||
start_over: Von vorne anfangen
|
||||
stop: Stoppen
|
||||
submit: Code zur Bewertung abgeben
|
||||
|
@ -176,6 +176,7 @@ en:
|
||||
destroy_file: Delete File
|
||||
download: Download
|
||||
dummy: No Action
|
||||
input: Your input
|
||||
lastsaved: 'Last saved: '
|
||||
network: 'While your code is running, port %{port} is accessible using the following address: <a href="%{address}" target="_blank">%{address}</a>.'
|
||||
render: Render
|
||||
@ -185,6 +186,7 @@ en:
|
||||
requestComments: Request comments
|
||||
save: Save
|
||||
score: Score
|
||||
send: Send
|
||||
start_over: Start over
|
||||
stop: Stop
|
||||
submit: Submit Code For Assessment
|
||||
|
@ -93,6 +93,7 @@ class DockerClient
|
||||
FileUtils.mkdir(local_workspace_path)
|
||||
container.start(container_start_options(execution_environment, local_workspace_path))
|
||||
container.start_time = Time.now
|
||||
container.status = :created
|
||||
container
|
||||
rescue Docker::Error::NotFoundError => error
|
||||
destroy_container(container)
|
||||
@ -125,8 +126,10 @@ class DockerClient
|
||||
container.stop.kill
|
||||
container.port_bindings.values.each { |port| PortPool.release(port) }
|
||||
clean_container_workspace(container)
|
||||
if(container)
|
||||
container.delete(force: true, v: true)
|
||||
end
|
||||
end
|
||||
|
||||
def execute_arbitrary_command(command, &block)
|
||||
execute_command(command, nil, block)
|
||||
@ -135,6 +138,7 @@ class DockerClient
|
||||
def execute_command(command, before_execution_block, output_consuming_block)
|
||||
#tries ||= 0
|
||||
@container = DockerContainerPool.get_container(@execution_environment)
|
||||
@container.status = :executing
|
||||
if @container
|
||||
before_execution_block.try(:call)
|
||||
send_command(command, @container, &output_consuming_block)
|
||||
@ -149,6 +153,7 @@ class DockerClient
|
||||
|
||||
def execute_websocket_command(command, before_execution_block, output_consuming_block)
|
||||
@container = DockerContainerPool.get_container(@execution_environment)
|
||||
@container.status = :executing
|
||||
if @container
|
||||
before_execution_block.try(:call)
|
||||
# todo catch exception if socket could not be created
|
||||
@ -169,21 +174,24 @@ class DockerClient
|
||||
Thread.new do
|
||||
timeout = @execution_environment.permitted_execution_time.to_i # seconds
|
||||
sleep(timeout)
|
||||
if container.status != :returned
|
||||
Rails.logger.info("Killing container after timeout of " + timeout.to_s + " seconds.")
|
||||
kill_container(container)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def exit_container(container)
|
||||
# if we use pooling and recylce the containers, put it back. otherwise, destroy it.
|
||||
(DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container)
|
||||
end
|
||||
|
||||
def kill_container(container)
|
||||
"""
|
||||
Please note that we cannot properly recycle containers when using
|
||||
websockets because it is impossible to determine whether a program
|
||||
is still running.
|
||||
"""
|
||||
# remove container from pool, then destroy it
|
||||
(DockerContainerPool.config[:active]) ? DockerContainerPool.remove_from_all_containers(container, @execution_environment) :
|
||||
if (DockerContainerPool.config[:active])
|
||||
DockerContainerPool.remove_from_all_containers(container, @execution_environment)
|
||||
end
|
||||
|
||||
#destroy container
|
||||
self.class.destroy_container(container)
|
||||
|
||||
# if we recylce containers, we start a fresh one
|
||||
@ -267,6 +275,7 @@ class DockerClient
|
||||
def self.return_container(container, execution_environment)
|
||||
clean_container_workspace(container)
|
||||
DockerContainerPool.return_container(container, execution_environment)
|
||||
container.status = :returned
|
||||
end
|
||||
#private :return_container
|
||||
|
||||
@ -285,7 +294,9 @@ class DockerClient
|
||||
Rails.logger.info('got timeout error for container ' + container.to_s)
|
||||
|
||||
# remove container from pool, then destroy it
|
||||
(DockerContainerPool.config[:active]) ? DockerContainerPool.remove_from_all_containers(container, @execution_environment) :
|
||||
if (DockerContainerPool.config[:active])
|
||||
DockerContainerPool.remove_from_all_containers(container, @execution_environment)
|
||||
end
|
||||
|
||||
# destroy container
|
||||
self.class.destroy_container(container)
|
||||
|
Reference in New Issue
Block a user