Files
codeocean/app/controllers/execution_environments_controller.rb
Konrad Hanff 90fac7b94c 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.
2021-11-01 17:12:48 +01:00

174 lines
5.5 KiB
Ruby

# frozen_string_literal: true
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]
def authorize!
authorize(@execution_environment || @execution_environments)
end
private :authorize!
def create
@execution_environment = ExecutionEnvironment.new(execution_environment_params)
authorize!
create_and_respond(object: @execution_environment) do
copy_execution_environment_to_poseidon
end
end
def destroy
destroy_and_respond(object: @execution_environment)
end
def edit
# Add the current execution_environment if not already present in the list
@docker_images |= [@execution_environment.docker_image]
end
def execute_command
@docker_client = DockerClient.new(execution_environment: @execution_environment)
render(json: @docker_client.execute_arbitrary_command(params[:command]))
end
def working_time_query
"
SELECT exercise_id, avg(working_time) as average_time, stddev_samp(extract('epoch' from working_time)) * interval '1 second' as stddev_time
FROM
(
SELECT user_id,
exercise_id,
sum(working_time_new) AS working_time
FROM
(SELECT user_id,
exercise_id,
CASE WHEN working_time >= #{StatisticsHelper::WORKING_TIME_DELTA_IN_SQL_INTERVAL} THEN '0' ELSE working_time END AS working_time_new
FROM
(SELECT user_id,
exercise_id,
id,
(created_at - lag(created_at) over (PARTITION BY user_id, exercise_id
ORDER BY created_at)) AS working_time
FROM submissions
WHERE exercise_id IN (SELECT ID FROM exercises WHERE execution_environment_id = #{@execution_environment.id})
GROUP BY exercise_id, user_id, id) AS foo) AS bar
GROUP BY user_id, exercise_id
) AS baz GROUP BY exercise_id;
"
end
def user_query
"
SELECT
id AS exercise_id,
COUNT(DISTINCT user_id) AS users,
AVG(score) AS average_score,
MAX(score) AS maximum_score,
stddev_samp(score) as stddev_score,
CASE
WHEN MAX(score)=0 THEN 0
ELSE 100 / MAX(score) * AVG(score)
END AS percent_correct,
SUM(submission_count) / COUNT(DISTINCT user_id) AS average_submission_count
FROM
(SELECT e.id,
s.user_id,
MAX(s.score) AS score,
COUNT(s.id) AS submission_count
FROM submissions s
JOIN exercises e ON e.id = s.exercise_id
WHERE e.execution_environment_id = #{@execution_environment.id}
GROUP BY e.id,
s.user_id) AS inner_query
GROUP BY id;
"
end
def statistics
working_time_statistics = {}
user_statistics = {}
ApplicationRecord.connection.execute(working_time_query).each do |tuple|
working_time_statistics[tuple['exercise_id'].to_i] = tuple
end
ApplicationRecord.connection.execute(user_query).each do |tuple|
user_statistics[tuple['exercise_id'].to_i] = tuple
end
render locals: {
working_time_statistics: working_time_statistics,
user_statistics: user_statistics,
}
end
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, :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
end
private :execution_environment_params
def index
@execution_environments = ExecutionEnvironment.all.includes(:user).order(:name).paginate(page: params[:page])
authorize!
end
def new
@execution_environment = ExecutionEnvironment.new
authorize!
end
def set_docker_images
DockerClient.check_availability!
@docker_images = DockerClient.image_tags.sort
rescue DockerClient::Error => e
@docker_images = []
flash[:warning] = e.message
Sentry.capture_exception(e)
end
private :set_docker_images
def set_execution_environment
@execution_environment = ExecutionEnvironment.find(params[:id])
authorize!
end
private :set_execution_environment
def set_testing_framework_adapters
Rails.application.eager_load!
@testing_framework_adapters = TestingFrameworkAdapter.descendants.sort_by(&:framework_name).map do |klass|
[klass.framework_name, klass.name]
end
end
private :set_testing_framework_adapters
def shell; end
def show
if @execution_environment.testing_framework?
@testing_framework_adapter = Kernel.const_get(@execution_environment.testing_framework)
end
end
def update
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