From: Ladislav Martincik lmartinc@redhat.com
--- src/app/controllers/instances_controller.rb | 16 +- src/app/controllers/pools_controller.rb | 3 - src/app/util/condormatic.rb | 155 +------------------ src/config/environment.rb | 2 +- src/lib/deltacloud_api/base_adapter.rb | 23 +++ src/lib/deltacloud_api/condor_adapter.rb | 168 ++++++++++++++++++++ src/lib/deltacloud_api/deltacloud_api.rb | 31 ++++ src/spec/lib/deltacloud_api/deltacloud_api_spec.rb | 32 ++++ 8 files changed, 266 insertions(+), 164 deletions(-) create mode 100644 src/lib/deltacloud_api/base_adapter.rb create mode 100644 src/lib/deltacloud_api/condor_adapter.rb create mode 100644 src/lib/deltacloud_api/deltacloud_api.rb create mode 100644 src/spec/lib/deltacloud_api/deltacloud_api_spec.rb
diff --git a/src/app/controllers/instances_controller.rb b/src/app/controllers/instances_controller.rb index 3ffb287..9e2b193 100644 --- a/src/app/controllers/instances_controller.rb +++ b/src/app/controllers/instances_controller.rb @@ -19,8 +19,6 @@ # Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers.
-require 'util/condormatic' - class InstancesController < ApplicationController before_filter :require_user, :get_nav_items before_filter :instance, :only => [:show, :key] @@ -111,7 +109,7 @@ class InstancesController < ApplicationController :task_target => @instance, :action => InstanceTask::ACTION_CREATE}) if @task.save - condormatic_instance_create(@task) + deltacloud_api.instance_create(@task) if Quota.can_start_instance?(@instance, nil) flash[:notice] = "Instance added." else @@ -167,11 +165,11 @@ class InstancesController < ApplicationController
case action when 'stop' - condormatic_instance_stop(@task) + deltacloud_api.instance_stop(@task) when 'destroy' - condormatic_instance_destroy(@task) + deltacloud_api.instance_destroy(@task) when 'start' - condormatic_instance_create(@task) + deltacloud_api.instance_create(@task) else raise ActionError.new("Sorry, action '#{action}' is currently not supported by condor backend.") end @@ -188,7 +186,7 @@ class InstancesController < ApplicationController action ='remove failed' raise ActionError.new("#{action} cannot be performed on this instance.") unless @instance.state == Instance::STATE_ERROR - condormatic_instance_reset_error(@instance) + deltacloud_api.instance_reset_error(@instance) action end
@@ -199,4 +197,8 @@ class InstancesController < ApplicationController require_privilege(Privilege::INSTANCE_VIEW, @instance.pool) end
+ def deltacloud_api + @deltacloud_api ||= DeltacloudAPI::Backend.new + end + end diff --git a/src/app/controllers/pools_controller.rb b/src/app/controllers/pools_controller.rb index 2ba6940..1c479b0 100644 --- a/src/app/controllers/pools_controller.rb +++ b/src/app/controllers/pools_controller.rb @@ -19,8 +19,6 @@ # Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers.
-require 'util/condormatic' - class PoolsController < ApplicationController before_filter :require_user, :get_nav_items
@@ -152,5 +150,4 @@ class PoolsController < ApplicationController end redirect_to :action => 'show', :id => @pool.id end - kick_condor end diff --git a/src/app/util/condormatic.rb b/src/app/util/condormatic.rb index 37040cd..02b7adb 100644 --- a/src/app/util/condormatic.rb +++ b/src/app/util/condormatic.rb @@ -20,143 +20,13 @@ require 'nokogiri' require 'socket'
-def escape(str) +module CondormaticHelper + def escape(str) str = str.gsub('\', '\\') str = str.gsub(' ', '\ ') -end - -def condormatic_instance_create(task) - - begin - instance = task.instance - realm = instance.realm rescue nil - - job_name = "job_#{instance.name}_#{instance.id}" - - instance.condor_job_id = job_name - instance.save! - - # I use the 2>&1 to get stderr and stdout together because popen3 does not support - # the ability to get the exit value of the command in ruby 1.8. - pipe = IO.popen("condor_submit 2>&1", "w+") - pipe.puts "universe = grid\n" - Rails.logger.info "universe = grid\n" - pipe.puts "executable = #{job_name}\n" - Rails.logger.info "executable = #{job_name}\n" - - resource = "grid_resource = dcloud $$(provider_url) $$(username) $$(password) $$(image_key) #{escape(instance.name)}" - if realm != nil - resource += " $$(realm_key)" - else - resource += " NULL" - end - resource += " $$(hardwareprofile_key) $$(keypair)\n" - - pipe.puts resource - Rails.logger.info resource - - requirements = "requirements = hardwareprofile == "#{instance.hardware_profile.id}" && image == "#{instance.template.id}"" - requirements += " && realm == "#{realm.id}"" if realm != nil - # We may need to add some stuff to the provider classads like pool id, provider id etc. This is mostly just - # to test and make sure this works for now. - requirements += " && deltacloud_quota_check("#{job_name}", other.cloud_account_id)" - requirements += "\n" - - pipe.puts requirements - Rails.logger.info requirements - - pipe.puts "notification = never\n" - Rails.logger.info "notification = never\n" - pipe.puts "queue\n" - Rails.logger.info "queue\n" - pipe.close_write - out = pipe.read - pipe.close - - Rails.logger.info "$? (return value?) is #{$?}" - raise ("Error calling condor_submit: #{out}") if $? != 0 - - rescue Exception => ex - task.state = Task::STATE_FAILED - Rails.logger.error ex.message - Rails.logger.error ex.backtrace - else - # FIXME: We're kinda lying here.. we don't know the state for the task but I don't think that matters so much - # as we are just going to use the 'task' table as a kind of audit log. - task.state = Task::STATE_PENDING - end - task.instance.save! -end - -# JobStatus for condor jobs: -# -# 0 Unexpanded U -# 1 Idle I -# 2 Running R -# 3 Removed X -# 4 Completed C -# 5 Held H -# 6 Submission_err E -# - -def condor_to_instance_state(state_val) - case state_val - when '0' - return Instance::STATE_PENDING - when '1' - return Instance::STATE_PENDING - when '2' - return Instance::STATE_RUNNING - when '3' - return Instance::STATE_STOPPED - when '4' - return Instance::STATE_STOPPED - when '5' - return Instance::STATE_ERROR - when '6' - return Instance::STATE_CREATE_FAILED - else - return Instance::STATE_PENDING end end
-def condormatic_instance_stop(task) - instance = task.instance_of?(InstanceTask) ? task.instance : task - - Rails.logger.info("calling condor_rm -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") - pipe = IO.popen("condor_rm -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") - out = pipe.read - pipe.close - - Rails.logger.info("condor_rm return status is #{$?}") - Rails.logger.error("Error calling condor_rm (exit code #{$?}) on job: #{out}") if $? != 0 -end - -def condormatic_instance_reset_error(instance) - - condormatic_instance_stop(instance) - Rails.logger.info("calling condor_rm -forcex -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") - pipe = IO.popen("condor_rm -forcex -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") - out = pipe.read - pipe.close - - Rails.logger.info("condor_rm return status is #{$?}") - Rails.logger.error("Error calling condor_rm (exit code #{$?}) on job: #{out}") if $? != 0 -end - -def condormatic_instance_destroy(task) - instance = task.instance - - Rails.logger.info("calling condor_rm -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") - pipe = IO.popen("condor_rm -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") - out = pipe.read - pipe.close - - Rails.logger.info("condor_rm return status is #{$?}") - Rails.logger.error("Error calling condor_rm (exit code #{$?}) on job: #{out}") if $? != 0 -end - - def condormatic_classads_sync Rails.logger.info "Starting condormatic_classads_sync..." index = 0 @@ -244,7 +114,6 @@ def condormatic_classads_sync pipe.puts "cloud_account_id="#{account.id}"" pipe.puts "keypair="#{escape(account.instance_key.name)}"" pipe.close_write - out = pipe.read pipe.close
@@ -254,23 +123,3 @@ def condormatic_classads_sync } Rails.logger.info "done" end - -def kick_condor - begin - socket = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0) - in_addr = Socket.pack_sockaddr_in(7890, 'localhost') - socket.connect(in_addr) - socket.write("kick") - socket.close - rescue - # if any of the above failed, it's possible that the condor_refreshd - # daemon is not running. This is especially useful when running the - # spec tests, since you don't necessarily want condor running in that - # circumstance. - # FIXME: there are a couple of problems with ignoring errors here. The - # first is that if this does actually fail, then it's unclear when in the - # future we will update the classads next. The second problem is that - # if condor_refreshd died for some reason, but condor itself is running, - # condor could be running with stale data. - end -end diff --git a/src/config/environment.rb b/src/config/environment.rb index 7d8ba2c..c3cad8b 100644 --- a/src/config/environment.rb +++ b/src/config/environment.rb @@ -51,7 +51,7 @@ Rails::Initializer.run do |config| config.gem "compass-960-plugin", :lib => "ninesixty" config.gem "simple-navigation" config.gem "typhoeus" - config.gem "rb-inotify" +# config.gem "rb-inotify"
config.active_record.observers = :instance_observer, :task_observer # Only load the plugins named here, in the order given. By default, all plugins diff --git a/src/lib/deltacloud_api/base_adapter.rb b/src/lib/deltacloud_api/base_adapter.rb new file mode 100644 index 0000000..527843a --- /dev/null +++ b/src/lib/deltacloud_api/base_adapter.rb @@ -0,0 +1,23 @@ +module DeltacloudAPI + + class NotImplemented < Exception; end + + class BaseAdapter + include Singleton + + # Basic API methods every Adapter should implement + def instance_create(*args) + raise NotImplemented.new + end + + def instance_stop(*args) + raise NotImplemented.new + end + + def instance_destroy(*args) + raise NotImplemented.new + end + + end + +end diff --git a/src/lib/deltacloud_api/condor_adapter.rb b/src/lib/deltacloud_api/condor_adapter.rb new file mode 100644 index 0000000..ab04afe --- /dev/null +++ b/src/lib/deltacloud_api/condor_adapter.rb @@ -0,0 +1,168 @@ +module DeltacloudAPI + class CondorAdapter < BaseAdapter + + def initialize + start + end + + def instance_create(task) + begin + instance = task.instance + realm = instance.realm rescue nil + + job_name = "job_#{instance.name}_#{instance.id}" + + instance.condor_job_id = job_name + instance.save! + + # I use the 2>&1 to get stderr and stdout together because popen3 does not support + # the ability to get the exit value of the command in ruby 1.8. + pipe = IO.popen("condor_submit 2>&1", "w+") + pipe.puts "universe = grid\n" + Rails.logger.info "universe = grid\n" + pipe.puts "executable = #{job_name}\n" + Rails.logger.info "executable = #{job_name}\n" + + resource = "grid_resource = dcloud $$(provider_url) $$(username) $$(password) $$(image_key) #{escape(instance.name)}" + if realm != nil + resource += " $$(realm_key)" + else + resource += " NULL" + end + resource += " $$(hardwareprofile_key) $$(keypair)\n" + + pipe.puts resource + Rails.logger.info resource + + requirements = "requirements = hardwareprofile == "#{instance.hardware_profile.id}" && image == "#{instance.template.id}"" + requirements += " && realm == "#{realm.id}"" if realm != nil + # We may need to add some stuff to the provider classads like pool id, provider id etc. This is mostly just + # to test and make sure this works for now. + requirements += " && deltacloud_quota_check("#{job_name}", other.cloud_account_id)" + requirements += "\n" + + pipe.puts requirements + Rails.logger.info requirements + + pipe.puts "notification = never\n" + Rails.logger.info "notification = never\n" + pipe.puts "queue\n" + Rails.logger.info "queue\n" + pipe.close_write + out = pipe.read + pipe.close + + Rails.logger.info "$? (return value?) is #{$?}" + raise ("Error calling condor_submit: #{out}") if $? != 0 + + rescue Exception => ex + task.state = Task::STATE_FAILED + Rails.logger.error ex.message + Rails.logger.error ex.backtrace + else + # FIXME: We're kinda lying here.. we don't know the state for the task but I don't think that matters so much + # as we are just going to use the 'task' table as a kind of audit log. + task.state = Task::STATE_PENDING + end + task.instance.save! + end + + def instance_stop(task) + instance = task.instance_of?(InstanceTask) ? task.instance : task + + Rails.logger.info("calling condor_rm -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") + pipe = IO.popen("condor_rm -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") + out = pipe.read + pipe.close + + Rails.logger.info("condor_rm return status is #{$?}") + Rails.logger.error("Error calling condor_rm (exit code #{$?}) on job: #{out}") if $? != 0 + end + + def instance_reset_error(instance) + instance_stop(instance) + Rails.logger.info("calling condor_rm -forcex -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") + pipe = IO.popen("condor_rm -forcex -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") + out = pipe.read + pipe.close + + Rails.logger.info("condor_rm return status is #{$?}") + Rails.logger.error("Error calling condor_rm (exit code #{$?}) on job: #{out}") if $? != 0 + end + + def instance_destroy(task) + instance = task.instance + + Rails.logger.info("calling condor_rm -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") + pipe = IO.popen("condor_rm -constraint 'Cmd == "#{instance.condor_job_id}"' 2>&1") + out = pipe.read + pipe.close + + Rails.logger.info("condor_rm return status is #{$?}") + Rails.logger.error("Error calling condor_rm (exit code #{$?}) on job: #{out}") if $? != 0 + end + + private + + def start + require 'socket' + begin + socket = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0) + in_addr = Socket.pack_sockaddr_in(7890, 'localhost') + socket.connect(in_addr) + socket.write("kick") + socket.close + rescue + # if any of the above failed, it's possible that the condor_refreshd + # daemon is not running. This is especially useful when running the + # spec tests, since you don't necessarily want condor running in that + # circumstance. + # FIXME: there are a couple of problems with ignoring errors here. The + # first is that if this does actually fail, then it's unclear when in the + # future we will update the classads next. The second problem is that + # if condor_refreshd died for some reason, but condor itself is running, + # condor could be running with stale data. + end + end + + private + + # JobStatus for condor jobs: + # + # 0 Unexpanded U + # 1 Idle I + # 2 Running R + # 3 Removed X + # 4 Completed C + # 5 Held H + # 6 Submission_err E + # + + def condor_to_instance_state(state_val) + case state_val + when '0' + return Instance::STATE_PENDING + when '1' + return Instance::STATE_PENDING + when '2' + return Instance::STATE_RUNNING + when '3' + return Instance::STATE_STOPPED + when '4' + return Instance::STATE_STOPPED + when '5' + return Instance::STATE_ERROR + when '6' + return Instance::STATE_CREATE_FAILED + else + return Instance::STATE_PENDING + end + end + + def escape(str) + str = str.gsub('\', '\\') + str = str.gsub(' ', '\ ') + end + + end +end diff --git a/src/lib/deltacloud_api/deltacloud_api.rb b/src/lib/deltacloud_api/deltacloud_api.rb new file mode 100644 index 0000000..58d55b2 --- /dev/null +++ b/src/lib/deltacloud_api/deltacloud_api.rb @@ -0,0 +1,31 @@ +require 'singleton' + +module DeltacloudAPI + + mattr_accessor :adapter + self.adapter = :condor + + class Backend + attr_reader :adapter + + def initialize(adapter = nil) + adapter = "#{adapter || DeltacloudAPI.adapter}_adapter" + adapter_clazz = "DeltacloudAPI::#{adapter.camelize}" + + # All adapters should behave as Singleton object + @adapter = adapter_clazz.constantize.instance + rescue NameError + # Try to require it before exiting with error + require File.join(Rails.root, 'lib', 'deltacloud_api', adapter) + @adapter = adapter_clazz.constantize.instance + end + + # For now we pass everything to adapter + # TODO: Should be restricted API based on BaseAdapter + def method_missing(sym, *args, &block) + @adapter.__send__(sym, *args, &block) + end + + end + +end diff --git a/src/spec/lib/deltacloud_api/deltacloud_api_spec.rb b/src/spec/lib/deltacloud_api/deltacloud_api_spec.rb new file mode 100644 index 0000000..3a5e8c0 --- /dev/null +++ b/src/spec/lib/deltacloud_api/deltacloud_api_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' +require 'deltacloud_api/deltacloud_api' + +describe DeltacloudAPI::Backend do + after do + DeltacloudAPI.adapter = :condor + end + + it "correctly loads default backend adapter" do + backend = DeltacloudAPI::Backend.new + backend.adapter.should be_a_kind_of DeltacloudAPI::CondorAdapter + end + + it "correctly loads non default backend adapter" do + class ::DeltacloudAPI::LocalMockAdapter < ::DeltacloudAPI::BaseAdapter; end + DeltacloudAPI.adapter = :local_mock + backend = DeltacloudAPI::Backend.new + backend.adapter.should be_a_kind_of ::DeltacloudAPI::LocalMockAdapter + end + + it "throws exceptions if adapter not present" do + DeltacloudAPI.adapter = :nonsense_1234 + lambda do + backend = DeltacloudAPI::Backend.new + end.should raise_error MissingSourceFile + end + + it "forwards all non defined calls to adapter" do + backend = DeltacloudAPI::Backend.new + end + +end