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:
Konrad Hanff
2021-06-01 14:07:35 +02:00
committed by Sebastian Serth
parent b762c73ddd
commit 90fac7b94c
10 changed files with 100 additions and 13 deletions

View File

@ -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

View File

@ -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
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 end

View File

@ -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,

View File

@ -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')

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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|