Copy execution environment to Poseidon on create and update
When creating or updating an execution environment, an API call to Poseidon is made with the necessary information to create the corresponding Nomad job. If runner management is configured, his will display a warning (currently in the same color as if it were a success) in the UI, if the API call fails. The environment is saved even if it fails. If runner management is not configured, this warning will not be created.
This commit is contained in:

committed by
Sebastian Serth

parent
b762c73ddd
commit
90fac7b94c
@ -5,10 +5,13 @@ module CommonBehavior
|
|||||||
@object = options[:object]
|
@object = options[:object]
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @object.save
|
if @object.save
|
||||||
yield if block_given?
|
notice = t('shared.object_created', model: @object.class.model_name.human)
|
||||||
|
if block_given?
|
||||||
|
result = yield
|
||||||
|
notice = result if result.present?
|
||||||
|
end
|
||||||
path = options[:path].try(:call) || @object
|
path = options[:path].try(:call) || @object
|
||||||
respond_with_valid_object(format, notice: t('shared.object_created', model: @object.class.model_name.human),
|
respond_with_valid_object(format, notice: notice, path: path, status: :created)
|
||||||
path: path, status: :created)
|
|
||||||
else
|
else
|
||||||
respond_with_invalid_object(format, template: :new)
|
respond_with_invalid_object(format, template: :new)
|
||||||
end
|
end
|
||||||
@ -42,9 +45,13 @@ path: path, status: :created)
|
|||||||
@object = options[:object]
|
@object = options[:object]
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @object.update(options[:params])
|
if @object.update(options[:params])
|
||||||
|
notice = t('shared.object_updated', model: @object.class.model_name.human)
|
||||||
|
if block_given?
|
||||||
|
result = yield
|
||||||
|
notice = result if result.present?
|
||||||
|
end
|
||||||
path = options[:path] || @object
|
path = options[:path] || @object
|
||||||
respond_with_valid_object(format, notice: t('shared.object_updated', model: @object.class.model_name.human),
|
respond_with_valid_object(format, notice: notice, path: path, status: :ok)
|
||||||
path: path, status: :ok)
|
|
||||||
else
|
else
|
||||||
respond_with_invalid_object(format, template: :edit)
|
respond_with_invalid_object(format, template: :edit)
|
||||||
end
|
end
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
class ExecutionEnvironmentsController < ApplicationController
|
class ExecutionEnvironmentsController < ApplicationController
|
||||||
include CommonBehavior
|
include CommonBehavior
|
||||||
|
|
||||||
|
RUNNER_MANAGEMENT_PRESENT = CodeOcean::Config.new(:code_ocean).read[:runner_management].present?
|
||||||
|
|
||||||
before_action :set_docker_images, only: %i[create edit new update]
|
before_action :set_docker_images, only: %i[create edit new update]
|
||||||
before_action :set_execution_environment, only: MEMBER_ACTIONS + %i[execute_command shell statistics]
|
before_action :set_execution_environment, only: MEMBER_ACTIONS + %i[execute_command shell statistics]
|
||||||
before_action :set_testing_framework_adapters, only: %i[create edit new update]
|
before_action :set_testing_framework_adapters, only: %i[create edit new update]
|
||||||
@ -15,7 +17,9 @@ class ExecutionEnvironmentsController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
@execution_environment = ExecutionEnvironment.new(execution_environment_params)
|
@execution_environment = ExecutionEnvironment.new(execution_environment_params)
|
||||||
authorize!
|
authorize!
|
||||||
create_and_respond(object: @execution_environment)
|
create_and_respond(object: @execution_environment) do
|
||||||
|
copy_execution_environment_to_poseidon
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@ -105,7 +109,7 @@ class ExecutionEnvironmentsController < ApplicationController
|
|||||||
|
|
||||||
def execution_environment_params
|
def execution_environment_params
|
||||||
if params[:execution_environment].present?
|
if params[:execution_environment].present?
|
||||||
params[:execution_environment].permit(:docker_image, :exposed_ports, :editor_mode, :file_extension, :file_type_id, :help, :indent_size, :memory_limit, :name, :network_enabled, :permitted_execution_time, :pool_size, :run_command, :test_command, :testing_framework).merge(
|
params[:execution_environment].permit(:docker_image, :exposed_ports, :editor_mode, :file_extension, :file_type_id, :help, :indent_size, :memory_limit, :cpu_limit, :name, :network_enabled, :permitted_execution_time, :pool_size, :run_command, :test_command, :testing_framework).merge(
|
||||||
user_id: current_user.id, user_type: current_user.class.name
|
user_id: current_user.id, user_type: current_user.class.name
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -155,6 +159,15 @@ class ExecutionEnvironmentsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
update_and_respond(object: @execution_environment, params: execution_environment_params)
|
update_and_respond(object: @execution_environment, params: execution_environment_params) do
|
||||||
|
copy_execution_environment_to_poseidon
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def copy_execution_environment_to_poseidon
|
||||||
|
unless RUNNER_MANAGEMENT_PRESENT && @execution_environment.copy_to_poseidon
|
||||||
|
t('execution_environments.form.errors.not_synced_to_poseidon')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private :copy_execution_environment_to_poseidon
|
||||||
|
end
|
||||||
|
@ -7,6 +7,10 @@ class ExecutionEnvironment < ApplicationRecord
|
|||||||
include DefaultValues
|
include DefaultValues
|
||||||
|
|
||||||
VALIDATION_COMMAND = 'whoami'
|
VALIDATION_COMMAND = 'whoami'
|
||||||
|
RUNNER_MANAGEMENT_PRESENT = CodeOcean::Config.new(:code_ocean).read[:runner_management].present?
|
||||||
|
BASE_URL = CodeOcean::Config.new(:code_ocean).read[:runner_management][:url] if RUNNER_MANAGEMENT_PRESENT
|
||||||
|
HEADERS = {'Content-Type' => 'application/json'}.freeze
|
||||||
|
DEFAULT_CPU_LIMIT = 20
|
||||||
|
|
||||||
after_initialize :set_default_values
|
after_initialize :set_default_values
|
||||||
|
|
||||||
@ -26,6 +30,8 @@ class ExecutionEnvironment < ApplicationRecord
|
|||||||
validates :permitted_execution_time, numericality: {only_integer: true}, presence: true
|
validates :permitted_execution_time, numericality: {only_integer: true}, presence: true
|
||||||
validates :pool_size, numericality: {only_integer: true}, presence: true
|
validates :pool_size, numericality: {only_integer: true}, presence: true
|
||||||
validates :run_command, presence: true
|
validates :run_command, presence: true
|
||||||
|
validates :cpu_limit, presence: true, numericality: {greater_than: 0, only_integer: true}
|
||||||
|
validates :exposed_ports, format: {with: /\A(([[:digit:]]{1,5},)*([[:digit:]]{1,5}))?\z/}
|
||||||
|
|
||||||
def set_default_values
|
def set_default_values
|
||||||
set_default_values_if_present(permitted_execution_time: 60, pool_size: 0)
|
set_default_values_if_present(permitted_execution_time: 60, pool_size: 0)
|
||||||
@ -36,6 +42,33 @@ class ExecutionEnvironment < ApplicationRecord
|
|||||||
name
|
name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def copy_to_poseidon
|
||||||
|
return false unless RUNNER_MANAGEMENT_PRESENT
|
||||||
|
|
||||||
|
url = "#{BASE_URL}/execution-environments/#{id}"
|
||||||
|
response = Faraday.put(url, to_json, HEADERS)
|
||||||
|
return true if [201, 204].include? response.status
|
||||||
|
|
||||||
|
Rails.logger.warn("Could not create execution environment in Poseidon, got response: #{response.as_json}")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_json(*_args)
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
image: docker_image,
|
||||||
|
prewarmingPoolSize: pool_size,
|
||||||
|
cpuLimit: cpu_limit,
|
||||||
|
memoryLimit: memory_limit,
|
||||||
|
networkAccess: network_enabled,
|
||||||
|
exposedPorts: exposed_ports_list,
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def exposed_ports_list
|
||||||
|
(exposed_ports || '').split(',').map(&:to_i)
|
||||||
|
end
|
||||||
|
|
||||||
def valid_test_setup?
|
def valid_test_setup?
|
||||||
if test_command? ^ testing_framework?
|
if test_command? ^ testing_framework?
|
||||||
errors.add(:test_command,
|
errors.add(:test_command,
|
||||||
|
@ -15,11 +15,14 @@
|
|||||||
.help-block.form-text == t('.hints.docker_image')
|
.help-block.form-text == t('.hints.docker_image')
|
||||||
.form-group
|
.form-group
|
||||||
= f.label(:exposed_ports)
|
= f.label(:exposed_ports)
|
||||||
= f.text_field(:exposed_ports, class: 'form-control', placeholder: '3000, 4000')
|
= f.text_field(:exposed_ports, class: 'form-control', placeholder: '3000,4000', pattern: '^((\d{1,5},)*(\d{1,5}))?$')
|
||||||
.help-block.form-text == t('.hints.exposed_ports')
|
.help-block.form-text == t('.hints.exposed_ports')
|
||||||
.form-group
|
.form-group
|
||||||
= f.label(:memory_limit)
|
= f.label(:memory_limit)
|
||||||
= f.number_field(:memory_limit, class: 'form-control', min: DockerClient::MINIMUM_MEMORY_LIMIT, value: f.object.memory_limit || DockerClient::DEFAULT_MEMORY_LIMIT)
|
= f.number_field(:memory_limit, class: 'form-control', min: DockerClient::MINIMUM_MEMORY_LIMIT, value: f.object.memory_limit || DockerClient::DEFAULT_MEMORY_LIMIT)
|
||||||
|
.form-group
|
||||||
|
= f.label(:cpu_limit)
|
||||||
|
= f.number_field(:cpu_limit, class: 'form-control', min: 1, step: 1, value: ExecutionEnvironment::DEFAULT_CPU_LIMIT)
|
||||||
.form-check.mb-3
|
.form-check.mb-3
|
||||||
label.form-check-label
|
label.form-check-label
|
||||||
= f.check_box(:network_enabled, class: 'form-check-input')
|
= f.check_box(:network_enabled, class: 'form-check-input')
|
||||||
|
@ -5,7 +5,7 @@ h1
|
|||||||
= row(label: 'execution_environment.name', value: @execution_environment.name)
|
= row(label: 'execution_environment.name', value: @execution_environment.name)
|
||||||
= row(label: 'execution_environment.user', value: link_to_if(policy(@execution_environment.author).show?, @execution_environment.author, @execution_environment.author))
|
= row(label: 'execution_environment.user', value: link_to_if(policy(@execution_environment.author).show?, @execution_environment.author, @execution_environment.author))
|
||||||
= row(label: 'execution_environment.file_type', value: @execution_environment.file_type.present? ? link_to(@execution_environment.file_type, @execution_environment.file_type) : nil)
|
= row(label: 'execution_environment.file_type', value: @execution_environment.file_type.present? ? link_to(@execution_environment.file_type, @execution_environment.file_type) : nil)
|
||||||
- [:docker_image, :exposed_ports, :memory_limit, :network_enabled, :permitted_execution_time, :pool_size].each do |attribute|
|
- [:docker_image, :exposed_ports, :memory_limit, :cpu_limit, :network_enabled, :permitted_execution_time, :pool_size].each do |attribute|
|
||||||
= row(label: "execution_environment.#{attribute}", value: @execution_environment.send(attribute))
|
= row(label: "execution_environment.#{attribute}", value: @execution_environment.send(attribute))
|
||||||
- [:run_command, :test_command].each do |attribute|
|
- [:run_command, :test_command].each do |attribute|
|
||||||
= row(label: "execution_environment.#{attribute}") do
|
= row(label: "execution_environment.#{attribute}") do
|
||||||
|
@ -14,6 +14,7 @@ de:
|
|||||||
file_type_id: Standard-Dateityp
|
file_type_id: Standard-Dateityp
|
||||||
help: Hilfetext
|
help: Hilfetext
|
||||||
memory_limit: Speicher-Limit (in MB)
|
memory_limit: Speicher-Limit (in MB)
|
||||||
|
cpu_limit: CPU-Limit (in MHz)
|
||||||
network_enabled: Netzwerkzugriff
|
network_enabled: Netzwerkzugriff
|
||||||
name: Name
|
name: Name
|
||||||
permitted_execution_time: Erlaubte Ausführungszeit (in Sekunden)
|
permitted_execution_time: Erlaubte Ausführungszeit (in Sekunden)
|
||||||
@ -281,7 +282,9 @@ de:
|
|||||||
hints:
|
hints:
|
||||||
command: <em>filename</em> wird automatisch durch den richtigen Dateinamen ersetzt.
|
command: <em>filename</em> wird automatisch durch den richtigen Dateinamen ersetzt.
|
||||||
docker_image: 'Wählen Sie ein Docker-Image aus der Liste oder fügen Sie ein neues hinzu, welches über <a href="https://hub.docker.com/" target="_blank">DockerHub</a> verfügbar ist.'
|
docker_image: 'Wählen Sie ein Docker-Image aus der Liste oder fügen Sie ein neues hinzu, welches über <a href="https://hub.docker.com/" target="_blank">DockerHub</a> verfügbar ist.'
|
||||||
exposed_ports: Während der Ausführung sind diese Ports für den Nutzer zugänglich.
|
exposed_ports: Während der Ausführung sind diese Ports für den Nutzer zugänglich. Die Portnummern müssen mit Komma, aber ohne Leerzeichen voneinander getrennt sein.
|
||||||
|
errors:
|
||||||
|
not_synced_to_poseidon: Die Ausführungsumgebung wurde erstellt, aber aufgrund eines Fehlers nicht zu Poseidon synchronisiert.
|
||||||
index:
|
index:
|
||||||
shell: Shell
|
shell: Shell
|
||||||
shell:
|
shell:
|
||||||
|
@ -14,6 +14,7 @@ en:
|
|||||||
file_type_id: Default File Type
|
file_type_id: Default File Type
|
||||||
help: Help Text
|
help: Help Text
|
||||||
memory_limit: Memory Limit (in MB)
|
memory_limit: Memory Limit (in MB)
|
||||||
|
cpu_limit: CPU Limit (in MHz)
|
||||||
name: Name
|
name: Name
|
||||||
network_enabled: Network Enabled
|
network_enabled: Network Enabled
|
||||||
permitted_execution_time: Permitted Execution Time (in Seconds)
|
permitted_execution_time: Permitted Execution Time (in Seconds)
|
||||||
@ -281,7 +282,9 @@ en:
|
|||||||
hints:
|
hints:
|
||||||
command: <em>filename</em> is automatically replaced with the correct filename.
|
command: <em>filename</em> is automatically replaced with the correct filename.
|
||||||
docker_image: Pick a Docker image listed above or add a new one which is available via <a href="https://hub.docker.com/" target="_blank">DockerHub</a>.
|
docker_image: Pick a Docker image listed above or add a new one which is available via <a href="https://hub.docker.com/" target="_blank">DockerHub</a>.
|
||||||
exposed_ports: During code execution these ports are accessible for the user.
|
exposed_ports: During code execution these ports are accessible for the user. Port numbers must be separated by a comma but no space.
|
||||||
|
errors:
|
||||||
|
not_synced_to_poseidon: The ExecutionEnvironment was created but not synced to Poseidon due to an error.
|
||||||
index:
|
index:
|
||||||
shell: Shell
|
shell: Shell
|
||||||
shell:
|
shell:
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddCpuLimitToExecutionEnvironment < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :execution_environments, :cpu_limit, :integer, default: 20
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CleanExposedPortsInExecutionEnvironment < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
ExecutionEnvironment.all.each do |execution_environment|
|
||||||
|
continue if execution_environment.exposed_ports.nil?
|
||||||
|
|
||||||
|
cleaned = execution_environment.exposed_ports.gsub(/[[:space:]]/, '')
|
||||||
|
list = cleaned.split(',').map(&:to_i).uniq
|
||||||
|
if list.empty?
|
||||||
|
execution_environment.update(exposed_ports: nil)
|
||||||
|
else
|
||||||
|
execution_environment.update(exposed_ports: list.join(','))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2021_05_19_134938) do
|
ActiveRecord::Schema.define(version: 2021_06_02_071834) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_trgm"
|
enable_extension "pg_trgm"
|
||||||
@ -112,6 +112,7 @@ ActiveRecord::Schema.define(version: 2021_05_19_134938) do
|
|||||||
t.integer "file_type_id"
|
t.integer "file_type_id"
|
||||||
t.integer "memory_limit"
|
t.integer "memory_limit"
|
||||||
t.boolean "network_enabled"
|
t.boolean "network_enabled"
|
||||||
|
t.integer "cpu_limit"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "exercise_collection_items", id: :serial, force: :cascade do |t|
|
create_table "exercise_collection_items", id: :serial, force: :cascade do |t|
|
||||||
|
Reference in New Issue
Block a user