diff --git a/lib/coursemology/evaluator.rb b/lib/coursemology/evaluator.rb index 2f4d7c1..70ec138 100644 --- a/lib/coursemology/evaluator.rb +++ b/lib/coursemology/evaluator.rb @@ -13,6 +13,7 @@ module Coursemology::Evaluator extend ActiveSupport::Autoload autoload :Client + autoload :DockerContainer autoload :CLI autoload :Models autoload :Services diff --git a/lib/coursemology/evaluator/docker_container.rb b/lib/coursemology/evaluator/docker_container.rb new file mode 100644 index 0000000..5d7e355 --- /dev/null +++ b/lib/coursemology/evaluator/docker_container.rb @@ -0,0 +1,31 @@ +class Coursemology::Evaluator::DockerContainer < Docker::Container + class << self + def create(image, argv: nil) + pull_image(image) + + ActiveSupport::Notifications.instrument('create.docker.evaluator.coursemology', + image: image) do |payload| + options = { 'Image' => image } + options['Cmd'] = argv if argv && !argv.empty? + + payload[:container] = super(options) + end + end + + private + + def pull_image(image) + ActiveSupport::Notifications.instrument('pull.docker.evaluator.coursemology', + image: image) do + Docker::Image.create('fromImage' => image) + end + end + end + + def delete + ActiveSupport::Notifications.instrument('destroy.docker.evaluator.coursemology', + container: id) do + super + end + end +end diff --git a/lib/coursemology/evaluator/services/evaluate_programming_package_service.rb b/lib/coursemology/evaluator/services/evaluate_programming_package_service.rb index b1af14b..65a9fc9 100644 --- a/lib/coursemology/evaluator/services/evaluate_programming_package_service.rb +++ b/lib/coursemology/evaluator/services/evaluate_programming_package_service.rb @@ -10,6 +10,9 @@ class Coursemology::Evaluator::Services::EvaluateProgrammingPackageService # The path to where the test report will be at. REPORT_PATH = File.join(PACKAGE_PATH, 'report.xml') + # The ratio to multiply the memory limits from our evaluation to the container by. + MEMORY_LIMIT_RATIO = 1.megabyte / 1.kilobyte + # Executes the given package in a container. # # @param [Coursemology::Evaluator::Models::ProgrammingEvaluation] evaluation The evaluation @@ -43,15 +46,15 @@ def execute def create_container(image) image_identifier = "coursemology/evaluator-image-#{image}" - ActiveSupport::Notifications.instrument('pull.docker.evaluator.coursemology', - image: image_identifier) do - Docker::Image.create('fromImage' => image_identifier) - end + Coursemology::Evaluator::DockerContainer.create(image_identifier, argv: container_arguments) + end - ActiveSupport::Notifications.instrument('create.docker.evaluator.coursemology', - image: image_identifier) do |payload| - payload[:container] = Docker::Container.create('Image' => image_identifier) - end + def container_arguments + result = [] + result.push("-c#{@evaluation.time_limit}") if @evaluation.time_limit + result.push("-m#{@evaluation.memory_limit * MEMORY_LIMIT_RATIO}") if @evaluation.memory_limit + + result end # Copies the contents of the package to the container. @@ -156,9 +159,6 @@ def extract_test_report_archive(container) end def destroy_container(container) - ActiveSupport::Notifications.instrument('destroy.docker.evaluator.coursemology', - container: container.id) do - container.delete - end + container.delete end end diff --git a/spec/coursemology/evaluator/docker_container_spec.rb b/spec/coursemology/evaluator/docker_container_spec.rb new file mode 100644 index 0000000..d6648ae --- /dev/null +++ b/spec/coursemology/evaluator/docker_container_spec.rb @@ -0,0 +1,20 @@ +RSpec.describe Coursemology::Evaluator::DockerContainer do + let(:image) { 'coursemology/evaluator-image-python:2.7' } + subject { Coursemology::Evaluator::DockerContainer.create(image) } + + describe '.create' do + it 'instruments the pull' do + expect { subject }.to instrument_notification('pull.docker.evaluator.coursemology') + end + + it 'instruments the creation' do + expect { subject }.to instrument_notification('create.docker.evaluator.coursemology') + end + end + + describe '#delete' do + it 'instruments the destruction' do + expect { subject.delete }.to instrument_notification('destroy.docker.evaluator.coursemology') + end + end +end diff --git a/spec/coursemology/evaluator/services/evaluate_programming_package_service_spec.rb b/spec/coursemology/evaluator/services/evaluate_programming_package_service_spec.rb index c0d436f..b57b81a 100644 --- a/spec/coursemology/evaluator/services/evaluate_programming_package_service_spec.rb +++ b/spec/coursemology/evaluator/services/evaluate_programming_package_service_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' RSpec.describe Coursemology::Evaluator::Services::EvaluateProgrammingPackageService do - let(:package) { build(:programming_evaluation) } + let(:package) { build(:programming_evaluation, *package_params) } + let(:package_params) { nil } + let(:image) { 'python:2.7' } subject { Coursemology::Evaluator::Services::EvaluateProgrammingPackageService.new(package) } describe '.execute' do @@ -12,27 +14,26 @@ end describe '#create_container' do - let(:image) { 'python:2.7' } let(:container) { subject.send(:create_container, image) } it 'prefixes the image with coursemology/evaluator-image' do - expect(Docker::Image).to \ - receive(:create).with('fromImage' => "coursemology/evaluator-image-#{image}"). - and_call_original - expect(Docker::Container).to \ - receive(:create).with('Image' => "coursemology/evaluator-image-#{image}") + expect(Coursemology::Evaluator::DockerContainer).to \ + receive(:create).with("coursemology/evaluator-image-#{image}", + hash_including(argv: [])) container end - it 'instruments the pull' do - expect { subject.send(:create_container, image) }.to \ - instrument_notification('pull.docker.evaluator.coursemology') - end + context 'when the evaluation has resource limits' do + let(:package_params) { [time_limit: 5, memory_limit: 16] } + + it 'specifies them to the container' do + expect(Coursemology::Evaluator::DockerContainer).to \ + receive(:create).with("coursemology/evaluator-image-#{image}", + hash_including(argv: ['-c5', '-m16384'])) - it 'instruments the creation' do - expect { subject.send(:create_container, image) }.to \ - instrument_notification('create.docker.evaluator.coursemology') + subject.send(:create_container, image) + end end end @@ -62,7 +63,6 @@ end describe '#execute_package' do - let(:image) { 'python:2.7' } let(:container) { subject.send(:create_container, image) } after { subject.send(:destroy_container, container) } @@ -83,7 +83,6 @@ def evaluate_result end describe '#execute_package_wait' do - let(:image) { 'python:2.7' } let(:container) { subject.send(:create_container, image) } after { subject.send(:destroy_container, container) } @@ -101,7 +100,6 @@ def evaluate_result end describe '#extract_result' do - let(:image) { 'python:2.7' } let(:container) do subject.send(:create_container, image).tap do |container| subject.send(:execute_package, container) @@ -117,7 +115,6 @@ def evaluate_result end describe '#extract_test_report' do - let(:image) { 'python:2.7' } let(:report_path) { File.join(__dir__, '../../../fixtures/sample_report.xml') } let(:report_contents) { File.read(report_path) } let(:container) { subject.send(:create_container, image) } @@ -144,14 +141,4 @@ def copy_dummy_report end end end - - describe '#destroy_container' do - let(:image) { 'python:2.7' } - let(:container) { subject.send(:create_container, image) } - - it 'instruments the destruction' do - expect { subject.send(:destroy_container, container) }.to \ - instrument_notification('destroy.docker.evaluator.coursemology') - end - end end diff --git a/spec/factories/programming_evaluations.rb b/spec/factories/programming_evaluations.rb index 0c81517..0fd3de1 100644 --- a/spec/factories/programming_evaluations.rb +++ b/spec/factories/programming_evaluations.rb @@ -10,8 +10,8 @@ def self.define_package(evaluation) factory :programming_evaluation, class: Coursemology::Evaluator::Models::ProgrammingEvaluation do id 1 language Coursemology::Polyglot::Language::Python::Python2Point7.name - memory_limit 32 - time_limit 5 + memory_limit nil + time_limit nil after(:stub) do |evaluation| evaluation.define_singleton_method(:save) {}