Remove usage of DockerClient

This commit is contained in:
Sebastian Serth
2021-10-29 22:33:35 +02:00
parent 20064b0715
commit 01ec9343cf
9 changed files with 60 additions and 65 deletions

View File

@ -9,7 +9,9 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy
end end
def self.initialize_environment def self.initialize_environment
DockerClient.initialize_environment unless Rails.env.test? && `which docker`.blank? raise Error.new('Docker configuration missing!') unless config[:host] && config[:workspace_root]
FileUtils.mkdir_p(File.expand_path(config[:workspace_root]))
end end
def self.sync_environment(_environment) def self.sync_environment(_environment)

View File

@ -12,8 +12,6 @@ describe ExecutionEnvironmentsController do
end end
describe 'POST #create' do describe 'POST #create' do
before { allow(DockerClient).to receive(:image_tags).at_least(:once).and_return([]) }
context 'with a valid execution environment' do context 'with a valid execution environment' do
let(:perform_request) { proc { post :create, params: {execution_environment: FactoryBot.build(:ruby).attributes} } } let(:perform_request) { proc { post :create, params: {execution_environment: FactoryBot.build(:ruby).attributes} } }
@ -61,7 +59,6 @@ describe ExecutionEnvironmentsController do
describe 'GET #edit' do describe 'GET #edit' do
before do before do
allow(DockerClient).to receive(:image_tags).at_least(:once).and_return([])
get :edit, params: {id: execution_environment.id} get :edit, params: {id: execution_environment.id}
end end
@ -99,7 +96,6 @@ describe ExecutionEnvironmentsController do
describe 'GET #new' do describe 'GET #new' do
before do before do
allow(DockerClient).to receive(:image_tags).at_least(:once).and_return([])
get :new get :new
end end
@ -115,8 +111,7 @@ describe ExecutionEnvironmentsController do
before do before do
allow(Runner).to receive(:strategy_class).and_return Runner::Strategy::DockerContainerPool allow(Runner).to receive(:strategy_class).and_return Runner::Strategy::DockerContainerPool
allow(DockerClient).to receive(:check_availability!).at_least(:once) allow(Runner::Strategy::DockerContainerPool).to receive(:available_images).and_return(docker_images)
allow(DockerClient).to receive(:image_tags).and_return(docker_images)
controller.send(:set_docker_images) controller.send(:set_docker_images)
end end
@ -128,7 +123,7 @@ describe ExecutionEnvironmentsController do
before do before do
allow(Runner).to receive(:strategy_class).and_return Runner::Strategy::DockerContainerPool allow(Runner).to receive(:strategy_class).and_return Runner::Strategy::DockerContainerPool
allow(DockerClient).to receive(:check_availability!).at_least(:once).and_raise(DockerClient::Error.new(error_message)) allow(Runner::Strategy::DockerContainerPool).to receive(:available_images).and_raise(Runner::Error::InternalServerError.new(error_message))
controller.send(:set_docker_images) controller.send(:set_docker_images)
end end
@ -168,7 +163,6 @@ describe ExecutionEnvironmentsController do
describe 'PUT #update' do describe 'PUT #update' do
context 'with a valid execution environment' do context 'with a valid execution environment' do
before do before do
allow(DockerClient).to receive(:image_tags).at_least(:once).and_return([])
allow(controller).to receive(:sync_to_runner_management).and_return(nil) allow(controller).to receive(:sync_to_runner_management).and_return(nil)
put :update, params: {execution_environment: FactoryBot.attributes_for(:ruby), id: execution_environment.id} put :update, params: {execution_environment: FactoryBot.attributes_for(:ruby), id: execution_environment.id}
end end

View File

@ -156,7 +156,6 @@ describe SubmissionsController do
context 'when no errors occur during execution' do context 'when no errors occur during execution' do
before do before do
allow_any_instance_of(DockerClient).to receive(:execute_run_command).with(submission, filename).and_return({})
perform_request perform_request
end end
@ -219,7 +218,6 @@ describe SubmissionsController do
let(:output) { {} } let(:output) { {} }
before do before do
allow_any_instance_of(DockerClient).to receive(:execute_test_command).with(submission, filename)
get :test, params: {filename: filename, id: submission.id} get :test, params: {filename: filename, id: submission.id}
end end

View File

@ -16,6 +16,7 @@ describe 'seeds' do
allow(ActiveRecord::Base).to receive(:establish_connection).with(:development) { allow(ActiveRecord::Base).to receive(:establish_connection).with(:development) {
ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.establish_connection(:test)
} }
allow_any_instance_of(ExecutionEnvironment).to receive(:working_docker_image?).and_return true
end end
describe 'execute db:seed', cleaning_strategy: :truncation do describe 'execute db:seed', cleaning_strategy: :truncation do

View File

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe 'Factories' do describe 'Factories' do
it 'are all valid', docker: true, permitted_execution_time: 30 do it 'are all valid', permitted_execution_time: 30 do
expect { FactoryBot.lint }.not_to raise_error expect { FactoryBot.lint }.not_to raise_error
end end
end end

View File

@ -5,7 +5,7 @@ require 'seeds_helper'
WORKSPACE_PATH = Rails.root.join('tmp', 'files', Rails.env, 'code_ocean_test') WORKSPACE_PATH = Rails.root.join('tmp', 'files', Rails.env, 'code_ocean_test')
describe DockerClient, docker: true do describe DockerClient do
let(:command) { 'whoami' } let(:command) { 'whoami' }
let(:docker_client) { described_class.new(execution_environment: FactoryBot.build(:java), user: FactoryBot.build(:admin)) } let(:docker_client) { described_class.new(execution_environment: FactoryBot.build(:java), user: FactoryBot.build(:admin)) }
let(:execution_environment) { FactoryBot.build(:java) } let(:execution_environment) { FactoryBot.build(:java) }
@ -71,13 +71,15 @@ describe DockerClient, docker: true do
it 'uses the correct Docker image' do it 'uses the correct Docker image' do
expect(described_class).to receive(:find_image_by_tag).with(execution_environment.docker_image).and_call_original expect(described_class).to receive(:find_image_by_tag).with(execution_environment.docker_image).and_call_original
create_container container = create_container
described_class.destroy_container(container)
end end
it 'creates a unique directory' do it 'creates a unique directory' do
expect(described_class).to receive(:generate_local_workspace_path).and_call_original expect(described_class).to receive(:generate_local_workspace_path).and_call_original
expect(FileUtils).to receive(:mkdir).with(kind_of(String)).and_call_original expect(FileUtils).to receive(:mkdir).with(kind_of(String)).and_call_original
create_container container = create_container
described_class.destroy_container(container)
end end
it 'creates a container' do it 'creates a container' do
@ -91,22 +93,26 @@ describe DockerClient, docker: true do
result result
end end
expect(Docker::Container).to receive(:create).with(kind_of(Hash)).and_call_original expect(Docker::Container).to receive(:create).with(kind_of(Hash)).and_call_original
create_container container = create_container
described_class.destroy_container(container)
end end
it 'starts the container' do it 'starts the container' do
expect_any_instance_of(Docker::Container).to receive(:start).and_call_original expect_any_instance_of(Docker::Container).to receive(:start).and_call_original
create_container container = create_container
described_class.destroy_container(container)
end end
it 'configures mapped directories' do it 'configures mapped directories' do
expect(described_class).to receive(:mapped_directories).and_call_original expect(described_class).to receive(:mapped_directories).and_call_original
create_container container = create_container
described_class.destroy_container(container)
end end
it 'configures mapped ports' do it 'configures mapped ports' do
expect(described_class).to receive(:mapped_ports).with(execution_environment).and_call_original expect(described_class).to receive(:mapped_ports).with(execution_environment).and_call_original
create_container container = create_container
described_class.destroy_container(container)
end end
context 'when an error occurs' do context 'when an error occurs' do
@ -118,7 +124,9 @@ describe DockerClient, docker: true do
end end
it 'retries to create a container' do it 'retries to create a container' do
expect(create_container).to be_a(Docker::Container) container = create_container
expect(container).to be_a(Docker::Container)
described_class.destroy_container(container)
end end
end end
@ -162,6 +170,7 @@ describe DockerClient, docker: true do
end end
describe '#create_workspace_file' do describe '#create_workspace_file' do
let(:container) { Docker::Container.send(:new, Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex) }
let(:file) { FactoryBot.build(:file, content: 'puts 42') } let(:file) { FactoryBot.build(:file, content: 'puts 42') }
let(:file_path) { File.join(workspace_path, file.name_with_extension) } let(:file_path) { File.join(workspace_path, file.name_with_extension) }
@ -170,7 +179,7 @@ describe DockerClient, docker: true do
it 'creates a file' do it 'creates a file' do
expect(described_class).to receive(:local_workspace_path).at_least(:once).and_return(workspace_path) expect(described_class).to receive(:local_workspace_path).at_least(:once).and_return(workspace_path)
FileUtils.mkdir_p(workspace_path) FileUtils.mkdir_p(workspace_path)
docker_client.send(:create_workspace_file, container: CONTAINER, file: file) docker_client.send(:create_workspace_file, container: container, file: file)
expect(File.exist?(file_path)).to be true expect(File.exist?(file_path)).to be true
expect(File.new(file_path, 'r').read).to eq(file.content) expect(File.new(file_path, 'r').read).to eq(file.content)
end end
@ -197,13 +206,15 @@ describe DockerClient, docker: true do
end end
it 'deletes the container' do it 'deletes the container' do
expect(container).to receive(:delete).with(force: true, v: true) expect(container).to receive(:delete).with(force: true, v: true).and_call_original
end end
end end
describe '#execute_arbitrary_command' do describe '#execute_arbitrary_command' do
let(:execute_arbitrary_command) { docker_client.execute_arbitrary_command(command) } let(:execute_arbitrary_command) { docker_client.execute_arbitrary_command(command) }
after { described_class.destroy_container(docker_client.container) }
it 'creates a new container' do it 'creates a new container' do
expect(described_class).to receive(:create_container).and_call_original expect(described_class).to receive(:create_container).and_call_original
execute_arbitrary_command execute_arbitrary_command
@ -246,7 +257,10 @@ describe DockerClient, docker: true do
describe '#execute_run_command' do describe '#execute_run_command' do
let(:filename) { submission.exercise.files.detect {|file| file.role == 'main_file' }.name_with_extension } let(:filename) { submission.exercise.files.detect {|file| file.role == 'main_file' }.name_with_extension }
after { docker_client.send(:execute_run_command, submission, filename) } after do
docker_client.send(:execute_run_command, submission, filename)
described_class.destroy_container(docker_client.container)
end
it 'creates a new container' do it 'creates a new container' do
expect(described_class).to receive(:create_container).with(submission.execution_environment).and_call_original expect(described_class).to receive(:create_container).with(submission.execution_environment).and_call_original
@ -266,7 +280,10 @@ describe DockerClient, docker: true do
describe '#execute_test_command' do describe '#execute_test_command' do
let(:filename) { submission.exercise.files.detect {|file| file.role == 'teacher_defined_test' || file.role == 'teacher_defined_linter' }.name_with_extension } let(:filename) { submission.exercise.files.detect {|file| file.role == 'teacher_defined_test' || file.role == 'teacher_defined_linter' }.name_with_extension }
after { docker_client.send(:execute_test_command, submission, filename) } after do
docker_client.send(:execute_test_command, submission, filename)
described_class.destroy_container(docker_client.container)
end
it 'creates a new container' do it 'creates a new container' do
expect(described_class).to receive(:create_container).with(submission.execution_environment).and_call_original expect(described_class).to receive(:create_container).with(submission.execution_environment).and_call_original
@ -314,6 +331,8 @@ describe DockerClient, docker: true do
let(:container) { described_class.create_container(execution_environment) } let(:container) { described_class.create_container(execution_environment) }
let(:local_workspace_path) { described_class.local_workspace_path(container) } let(:local_workspace_path) { described_class.local_workspace_path(container) }
after { described_class.destroy_container(container) }
it 'returns a path' do it 'returns a path' do
expect(local_workspace_path).to be_a(Pathname) expect(local_workspace_path).to be_a(Pathname)
end end
@ -358,7 +377,10 @@ describe DockerClient, docker: true do
let(:container) { described_class.create_container(execution_environment) } let(:container) { described_class.create_container(execution_environment) }
let(:send_command) { docker_client.send(:send_command, command, container, &block) } let(:send_command) { docker_client.send(:send_command, command, container, &block) }
after { send_command } after do
send_command
described_class.destroy_container(container)
end
it 'limits the execution time' do it 'limits the execution time' do
expect(Timeout).to receive(:timeout).at_least(:once).with(kind_of(Numeric)).and_call_original expect(Timeout).to receive(:timeout).at_least(:once).with(kind_of(Numeric)).and_call_original

View File

@ -3,6 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe DockerContainerMixin do describe DockerContainerMixin do
let(:container) { Docker::Container.send(:new, Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex) }
describe '#binds' do describe '#binds' do
let(:binds) { [] } let(:binds) { [] }
@ -11,8 +13,8 @@ describe DockerContainerMixin do
end end
it 'returns the correct information' do it 'returns the correct information' do
allow(CONTAINER).to receive(:json).and_return('HostConfig' => {'Binds' => binds}) allow(container).to receive(:json).and_return('HostConfig' => {'Binds' => binds})
expect(CONTAINER.binds).to eq(binds) expect(container.binds).to eq(binds)
end end
end end
@ -25,8 +27,8 @@ describe DockerContainerMixin do
end end
it 'returns the correct information' do it 'returns the correct information' do
allow(CONTAINER).to receive(:json).and_return('HostConfig' => {'PortBindings' => port_bindings}) allow(container).to receive(:json).and_return('HostConfig' => {'PortBindings' => port_bindings})
expect(CONTAINER.port_bindings).to eq(port => port) expect(container.port_bindings).to eq(port => port)
end end
end end
end end

View File

@ -5,10 +5,11 @@ require 'rails_helper'
describe ExecutionEnvironment do describe ExecutionEnvironment do
let(:execution_environment) { described_class.create.tap {|execution_environment| execution_environment.update(network_enabled: nil) } } let(:execution_environment) { described_class.create.tap {|execution_environment| execution_environment.update(network_enabled: nil) } }
it 'validates that the Docker image works', docker: true do it 'validates that the Docker image works' do
allow(execution_environment).to receive(:validate_docker_image?).and_return(true) allow(execution_environment).to receive(:validate_docker_image?).and_return(true)
expect(execution_environment).to receive(:working_docker_image?) allow(execution_environment).to receive(:working_docker_image?).and_return(true)
execution_environment.update(docker_image: FactoryBot.attributes_for(:ruby)[:docker_image]) execution_environment.update(docker_image: FactoryBot.attributes_for(:ruby)[:docker_image])
expect(execution_environment).to have_received(:working_docker_image?)
end end
it 'validates the presence of a Docker image name' do it 'validates the presence of a Docker image name' do
@ -144,27 +145,29 @@ describe ExecutionEnvironment do
end end
end end
describe '#working_docker_image?', docker: true do describe '#working_docker_image?' do
let(:working_docker_image?) { execution_environment.send(:working_docker_image?) } let(:working_docker_image?) { execution_environment.send(:working_docker_image?) }
let(:runner) { instance_double 'runner' }
before { allow(DockerClient).to receive(:find_image_by_tag).and_return(Object.new) } before do
allow(Runner).to receive(:for).with(execution_environment.author, execution_environment).and_return runner
end
it 'instantiates a Runner' do it 'instantiates a Runner' do
runner = instance_double 'runner'
allow(Runner).to receive(:for).with(execution_environment.author, execution_environment).and_return runner
allow(runner).to receive(:execute_command).and_return({}) allow(runner).to receive(:execute_command).and_return({})
working_docker_image? working_docker_image?
expect(runner).to have_received(:execute_command).once expect(runner).to have_received(:execute_command).once
end end
it 'executes the validation command' do it 'executes the validation command' do
allow_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).with(ExecutionEnvironment::VALIDATION_COMMAND).and_return({}) allow(runner).to receive(:execute_command).and_return({})
working_docker_image? working_docker_image?
expect(runner).to have_received(:execute_command).with(ExecutionEnvironment::VALIDATION_COMMAND, raise_exception: true)
end end
context 'when the command produces an error' do context 'when the command produces an error' do
it 'adds an error' do it 'adds an error' do
allow_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).and_return(stderr: 'command not found') allow(runner).to receive(:execute_command).and_return(stderr: 'command not found')
working_docker_image? working_docker_image?
expect(execution_environment.errors[:docker_image]).to be_present expect(execution_environment.errors[:docker_image]).to be_present
end end
@ -172,7 +175,7 @@ describe ExecutionEnvironment do
context 'when the Docker client produces an error' do context 'when the Docker client produces an error' do
it 'adds an error' do it 'adds an error' do
allow_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).and_raise(DockerClient::Error) allow(runner).to receive(:execute_command).and_raise(Runner::Error)
working_docker_image? working_docker_image?
expect(execution_environment.errors[:docker_image]).to be_present expect(execution_environment.errors[:docker_image]).to be_present
end end

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
CONTAINER = Docker::Container.send(:new, Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex)
IMAGE = Docker::Image.new(Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex, 'RepoTags' => [FactoryBot.attributes_for(:ruby)[:docker_image]])
RSpec.configure do |config|
config.before(:each) do |example|
unless example.metadata[:docker]
allow(DockerClient).to receive(:check_availability!).and_return(true)
allow(DockerClient).to receive(:create_container).and_return(CONTAINER)
allow(DockerClient).to receive(:find_image_by_tag).and_return(IMAGE)
allow(DockerClient).to receive(:image_tags).and_return([IMAGE.info['RepoTags']].flatten)
allow(DockerClient).to receive(:local_workspace_path).and_return(Dir.mktmpdir)
allow_any_instance_of(DockerClient).to receive(:send_command).and_return({})
allow_any_instance_of(ExecutionEnvironment).to receive(:working_docker_image?)
end
end
config.after(:suite) do
examples = RSpec.world.filtered_examples.values.flatten
has_docker_tests = examples.any? {|example| example.metadata[:docker] }
next unless has_docker_tests
FileUtils.rm_rf(Rails.root.join('tmp/files/test'))
`which docker && test -n "$(docker ps --all --quiet)" && docker rm --force $(docker ps --all --quiet)`
end
end