From 6d1b388e3c41dfe4f97c71eb9b717cab6b5411ff Mon Sep 17 00:00:00 2001 From: Sebastian Serth Date: Sun, 24 Oct 2021 12:36:01 +0200 Subject: [PATCH] Reorder methods in strategy classes --- lib/runner/connection.rb | 2 +- lib/runner/strategy.rb | 28 +++-- lib/runner/strategy/docker_container_pool.rb | 56 +++++---- lib/runner/strategy/poseidon.rb | 122 ++++++++++--------- 4 files changed, 116 insertions(+), 92 deletions(-) diff --git a/lib/runner/connection.rb b/lib/runner/connection.rb index 02c65922..7a58bd8f 100644 --- a/lib/runner/connection.rb +++ b/lib/runner/connection.rb @@ -22,7 +22,7 @@ class Runner::Connection # Internally, Faye::WebSocket uses EventMachine and the `ping` value is used to wake the EventMachine thread # The `tls` option is used to customize the validation of TLS connections. # Passing `nil` as a `root_cert_file` is okay and done so for the DockerContainerPool. - @socket = Faye::WebSocket::Client.new(url, [], strategy.websocket_header.merge(ping: 0.1)) + @socket = Faye::WebSocket::Client.new(url, [], strategy.class.websocket_header.merge(ping: 0.1)) @strategy = strategy @status = :established @event_loop = event_loop diff --git a/lib/runner/strategy.rb b/lib/runner/strategy.rb index 9a19b77b..ce58a947 100644 --- a/lib/runner/strategy.rb +++ b/lib/runner/strategy.rb @@ -5,18 +5,10 @@ class Runner::Strategy @execution_environment = environment end - def self.config - raise NotImplementedError - end - def self.initialize_environment raise NotImplementedError end - def self.available_images - raise NotImplementedError - end - def self.sync_environment(_environment) raise NotImplementedError end @@ -33,11 +25,27 @@ class Runner::Strategy raise NotImplementedError end - def attach_to_execution(_command) + def attach_to_execution(_command, _event_loop) raise NotImplementedError end - def websocket_header + def self.available_images + raise NotImplementedError + end + + def self.config + raise NotImplementedError + end + + def self.release + raise NotImplementedError + end + + def self.pool_size + raise NotImplementedError + end + + def self.websocket_header raise NotImplementedError end end diff --git a/lib/runner/strategy/docker_container_pool.rb b/lib/runner/strategy/docker_container_pool.rb index 056da47d..df47fac0 100644 --- a/lib/runner/strategy/docker_container_pool.rb +++ b/lib/runner/strategy/docker_container_pool.rb @@ -3,22 +3,15 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy attr_reader :container_id, :command - def self.config - # Since the docker configuration file contains code that must be executed, we use ERB templating. - @config ||= CodeOcean::Config.new(:docker).read(erb: true) + def initialize(runner_id, _environment) + super + @container_id = runner_id end def self.initialize_environment DockerClient.initialize_environment unless Rails.env.test? && `which docker`.blank? end - def self.available_images - DockerClient.check_availability! - DockerClient.image_tags - rescue DockerClient::Error => e - raise Runner::Error::InternalServerError.new(e.message) - end - def self.sync_environment(_environment) # There is no dedicated sync mechanism yet true @@ -38,9 +31,14 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished new runner request" } end - def initialize(runner_id, _environment) - super - @container_id = runner_id + def destroy_at_management + url = "#{self.class.config[:pool][:location]}/docker_container_pool/destroy_container/#{container.id}" + Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Destroying runner at #{url}" } + Faraday.get(url) + rescue Faraday::Error => e + raise Runner::Error::FaradayError.new("Request to DockerContainerPool failed: #{e.inspect}") + ensure + Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished destroying runner" } end def copy_files(files) @@ -69,16 +67,6 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished copying files" } end - def destroy_at_management - url = "#{self.class.config[:pool][:location]}/docker_container_pool/destroy_container/#{container.id}" - Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Destroying runner at #{url}" } - Faraday.get(url) - rescue Faraday::Error => e - raise Runner::Error::FaradayError.new("Request to DockerContainerPool failed: #{e.inspect}") - ensure - Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished destroying runner" } - end - def attach_to_execution(command, event_loop) @command = command query_params = 'logs=0&stream=1&stderr=1&stdout=1&stdin=1' @@ -99,7 +87,27 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy socket end - def websocket_header + def self.available_images + DockerClient.check_availability! + DockerClient.image_tags + rescue DockerClient::Error => e + raise Runner::Error::InternalServerError.new(e.message) + end + + def self.config + # Since the docker configuration file contains code that must be executed, we use ERB templating. + @config ||= CodeOcean::Config.new(:docker).read(erb: true) + end + + def self.release + nil + end + + def self.pool_size + {} + end + + def self.websocket_header {} end diff --git a/lib/runner/strategy/poseidon.rb b/lib/runner/strategy/poseidon.rb index 15f7d8e9..88562759 100644 --- a/lib/runner/strategy/poseidon.rb +++ b/lib/runner/strategy/poseidon.rb @@ -9,8 +9,9 @@ class Runner::Strategy::Poseidon < Runner::Strategy end end - def self.config - @config ||= CodeOcean::Config.new(:code_ocean).read[:runner_management] || {} + def initialize(runner_id, _environment) + super + @allocation_id = runner_id end def self.initialize_environment @@ -18,17 +19,6 @@ class Runner::Strategy::Poseidon < Runner::Strategy nil end - def self.available_images - # Images are pulled when needed for a new execution environment - # and cleaned up automatically if no longer in use. - # Hence, there is no additional image that we need to return - [] - end - - def self.headers - @headers ||= {'Content-Type' => 'application/json', 'Poseidon-Token' => config[:token]} - end - def self.sync_environment(environment) url = "#{config[:url]}/execution-environments/#{environment.id}" connection = Faraday.new nil, ssl: {ca_file: config[:ca_file]} @@ -68,38 +58,15 @@ class Runner::Strategy::Poseidon < Runner::Strategy Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished new runner request" } end - def self.handle_error(response) - case response.status - when 400 - response_body = parse response - raise Runner::Error::BadRequest.new(response_body[:message]) - when 401 - raise Runner::Error::Unauthorized.new('Authentication with Poseidon failed') - when 404 - raise Runner::Error::RunnerNotFound.new - when 500 - response_body = parse response - error_code = response_body[:errorCode] - if error_code == error_nomad_overload - raise Runner::Error::NotAvailable.new("Poseidon has no runner available (#{error_code}): #{response_body[:message]}") - else - raise Runner::Error::InternalServerError.new("Poseidon sent #{response_body[:errorCode]}: #{response_body[:message]}") - end - else - raise Runner::Error::UnexpectedResponse.new("Poseidon sent unexpected response status code #{response.status}") - end - end - - def self.parse(response) - JSON.parse(response.body).deep_symbolize_keys - rescue JSON::ParserError => e - # Poseidon should not send invalid json - raise Runner::Error::UnexpectedResponse.new("Error parsing response from Poseidon: #{e.message}") - end - - def initialize(runner_id, _environment) - super - @allocation_id = runner_id + def destroy_at_management + Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Destroying runner at #{runner_url}" } + connection = Faraday.new nil, ssl: {ca_file: self.class.config[:ca_file]} + response = connection.delete runner_url, nil, self.class.headers + self.class.handle_error response unless response.status == 204 + rescue Faraday::Error => e + raise Runner::Error::FaradayError.new("Request to Poseidon failed: #{e.inspect}") + ensure + Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished destroying runner" } end def copy_files(files) @@ -135,24 +102,65 @@ class Runner::Strategy::Poseidon < Runner::Strategy socket end - def destroy_at_management - Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Destroying runner at #{runner_url}" } - connection = Faraday.new nil, ssl: {ca_file: self.class.config[:ca_file]} - response = connection.delete runner_url, nil, self.class.headers - self.class.handle_error response unless response.status == 204 - rescue Faraday::Error => e - raise Runner::Error::FaradayError.new("Request to Poseidon failed: #{e.inspect}") - ensure - Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished destroying runner" } + def self.available_images + # Images are pulled when needed for a new execution environment + # and cleaned up automatically if no longer in use. + # Hence, there is no additional image that we need to return + [] end - def websocket_header + def self.config + @config ||= CodeOcean::Config.new(:code_ocean).read[:runner_management] || {} + end + + def self.release + nil + end + + def self.pool_size + {} + end + + def self.websocket_header { - tls: {root_cert_file: self.class.config[:ca_file]}, - headers: {'Poseidon-Token' => self.class.config[:token]}, + tls: {root_cert_file: config[:ca_file]}, + headers: {'Poseidon-Token' => config[:token]}, } end + def self.handle_error(response) + case response.status + when 400 + response_body = parse response + raise Runner::Error::BadRequest.new(response_body[:message]) + when 401 + raise Runner::Error::Unauthorized.new('Authentication with Poseidon failed') + when 404 + raise Runner::Error::RunnerNotFound.new + when 500 + response_body = parse response + error_code = response_body[:errorCode] + if error_code == error_nomad_overload + raise Runner::Error::NotAvailable.new("Poseidon has no runner available (#{error_code}): #{response_body[:message]}") + else + raise Runner::Error::InternalServerError.new("Poseidon sent #{response_body[:errorCode]}: #{response_body[:message]}") + end + else + raise Runner::Error::UnexpectedResponse.new("Poseidon sent unexpected response status code #{response.status}") + end + end + + def self.headers + @headers ||= {'Content-Type' => 'application/json', 'Poseidon-Token' => config[:token]} + end + + def self.parse(response) + JSON.parse(response.body).deep_symbolize_keys + rescue JSON::ParserError => e + # Poseidon should not send invalid json + raise Runner::Error::UnexpectedResponse.new("Error parsing response from Poseidon: #{e.message}") + end + private def execute_command(command)