diff --git a/app/controllers/concerns/common_behavior.rb b/app/controllers/concerns/common_behavior.rb index 4c36cf63..8d63246d 100644 --- a/app/controllers/concerns/common_behavior.rb +++ b/app/controllers/concerns/common_behavior.rb @@ -5,10 +5,13 @@ module CommonBehavior @object = options[:object] respond_to do |format| 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 - respond_with_valid_object(format, notice: t('shared.object_created', model: @object.class.model_name.human), -path: path, status: :created) + respond_with_valid_object(format, notice: notice, path: path, status: :created) else respond_with_invalid_object(format, template: :new) end @@ -42,9 +45,13 @@ path: path, status: :created) @object = options[:object] respond_to do |format| 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 - respond_with_valid_object(format, notice: t('shared.object_updated', model: @object.class.model_name.human), -path: path, status: :ok) + respond_with_valid_object(format, notice: notice, path: path, status: :ok) else respond_with_invalid_object(format, template: :edit) end diff --git a/app/controllers/execution_environments_controller.rb b/app/controllers/execution_environments_controller.rb index 4449fa41..95132b09 100644 --- a/app/controllers/execution_environments_controller.rb +++ b/app/controllers/execution_environments_controller.rb @@ -3,6 +3,8 @@ class ExecutionEnvironmentsController < ApplicationController 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_execution_environment, only: MEMBER_ACTIONS + %i[execute_command shell statistics] before_action :set_testing_framework_adapters, only: %i[create edit new update] @@ -15,7 +17,9 @@ class ExecutionEnvironmentsController < ApplicationController def create @execution_environment = ExecutionEnvironment.new(execution_environment_params) authorize! - create_and_respond(object: @execution_environment) + create_and_respond(object: @execution_environment) do + copy_execution_environment_to_poseidon + end end def destroy @@ -105,7 +109,7 @@ class ExecutionEnvironmentsController < ApplicationController def execution_environment_params 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 ) end @@ -155,6 +159,15 @@ class ExecutionEnvironmentsController < ApplicationController end 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 + + 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 diff --git a/app/models/execution_environment.rb b/app/models/execution_environment.rb index e4d39a93..3db35c35 100644 --- a/app/models/execution_environment.rb +++ b/app/models/execution_environment.rb @@ -7,6 +7,10 @@ class ExecutionEnvironment < ApplicationRecord include DefaultValues 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 @@ -26,6 +30,8 @@ class ExecutionEnvironment < ApplicationRecord validates :permitted_execution_time, numericality: {only_integer: true}, presence: true validates :pool_size, numericality: {only_integer: true}, 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 set_default_values_if_present(permitted_execution_time: 60, pool_size: 0) @@ -36,6 +42,33 @@ class ExecutionEnvironment < ApplicationRecord name 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? if test_command? ^ testing_framework? errors.add(:test_command, diff --git a/app/views/execution_environments/_form.html.slim b/app/views/execution_environments/_form.html.slim index 13ee252e..9447d1c6 100644 --- a/app/views/execution_environments/_form.html.slim +++ b/app/views/execution_environments/_form.html.slim @@ -15,11 +15,14 @@ .help-block.form-text == t('.hints.docker_image') .form-group = 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') .form-group = 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) + .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 label.form-check-label = f.check_box(:network_enabled, class: 'form-check-input') diff --git a/app/views/execution_environments/show.html.slim b/app/views/execution_environments/show.html.slim index 21133a71..6d9fb32e 100644 --- a/app/views/execution_environments/show.html.slim +++ b/app/views/execution_environments/show.html.slim @@ -5,7 +5,7 @@ h1 = 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.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)) - [:run_command, :test_command].each do |attribute| = row(label: "execution_environment.#{attribute}") do diff --git a/config/locales/de.yml b/config/locales/de.yml index 29cf2eb4..344f1d58 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -14,6 +14,7 @@ de: file_type_id: Standard-Dateityp help: Hilfetext memory_limit: Speicher-Limit (in MB) + cpu_limit: CPU-Limit (in MHz) network_enabled: Netzwerkzugriff name: Name permitted_execution_time: Erlaubte Ausführungszeit (in Sekunden) @@ -281,7 +282,9 @@ de: hints: command: filename 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 DockerHub 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: shell: Shell shell: diff --git a/config/locales/en.yml b/config/locales/en.yml index 41c37dfc..183a1186 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -14,6 +14,7 @@ en: file_type_id: Default File Type help: Help Text memory_limit: Memory Limit (in MB) + cpu_limit: CPU Limit (in MHz) name: Name network_enabled: Network Enabled permitted_execution_time: Permitted Execution Time (in Seconds) @@ -281,7 +282,9 @@ en: hints: command: filename is automatically replaced with the correct filename. docker_image: Pick a Docker image listed above or add a new one which is available via DockerHub. - 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: shell: Shell shell: diff --git a/db/migrate/20210601095654_add_cpu_limit_to_execution_environment.rb b/db/migrate/20210601095654_add_cpu_limit_to_execution_environment.rb new file mode 100644 index 00000000..c74aa69a --- /dev/null +++ b/db/migrate/20210601095654_add_cpu_limit_to_execution_environment.rb @@ -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 diff --git a/db/migrate/20210602071834_clean_exposed_ports_in_execution_environment.rb b/db/migrate/20210602071834_clean_exposed_ports_in_execution_environment.rb new file mode 100644 index 00000000..32db9950 --- /dev/null +++ b/db/migrate/20210602071834_clean_exposed_ports_in_execution_environment.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index fb4dbc1d..beefc5b1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # 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 enable_extension "pg_trgm" @@ -112,6 +112,7 @@ ActiveRecord::Schema.define(version: 2021_05_19_134938) do t.integer "file_type_id" t.integer "memory_limit" t.boolean "network_enabled" + t.integer "cpu_limit" end create_table "exercise_collection_items", id: :serial, force: :cascade do |t|