Project

General

Profile

Download (5.79 KB) Statistics
| Branch: | Tag: | Revision:
2a0cffd3 Ohad Levy
require_dependency "proxy_api"
6454273f Paul Kelly
require 'orchestration/queue'
90b83222 Ohad Levy
module Orchestration
def self.included(base)
base.send :include, InstanceMethods
base.class_eval do
c7540a9c Amos Benari
attr_reader :queue, :post_queue, :old, :record_conflicts
90b83222 Ohad Levy
# stores actions to be performed on our proxies based on priority
before_validation :set_queue
before_validation :setup_clone

# extend our Host model to know how to handle subsystems
ac36e7ce Ohad Levy
include Orchestration::DNS
30ae12bf Ohad Levy
include Orchestration::DHCP
90b83222 Ohad Levy
include Orchestration::TFTP
36f93e4d Ohad Levy
include Orchestration::Puppetca
96be8845 Ohad Levy
include Orchestration::Libvirt
334d0359 Amos Benari
include Orchestration::Compute
dd42df0a Ohad Levy
include Orchestration::SSHProvision
90b83222 Ohad Levy
# save handles both creation and update of hosts
before_save :on_save
c7540a9c Amos Benari
after_commit :post_commit
822eef5a Paul Kelly
after_destroy :on_destroy
90b83222 Ohad Levy
end
end

module InstanceMethods

protected
def on_save
0363ab42 Amos Benari
process :queue
90b83222 Ohad Levy
end

c7540a9c Amos Benari
def post_commit
0363ab42 Amos Benari
process :post_queue
c7540a9c Amos Benari
end

822eef5a Paul Kelly
def on_destroy
0363ab42 Amos Benari
errors.empty? ? process(:queue) : false
822eef5a Paul Kelly
end

90b83222 Ohad Levy
def rollback
raise ActiveRecord::Rollback
end

# log and add to errors
30ae12bf Ohad Levy
def failure msg, backtrace=nil, dest = :base
e85584bf Paul Kelly
logger.warn(backtrace ? msg + backtrace.join("\n") : msg)
30ae12bf Ohad Levy
errors.add dest, msg
90b83222 Ohad Levy
false
end

public

# we override this method in order to include checking the
# after validation callbacks status, as rails by default does
# not care about their return status.
017e1049 Ohad Levy
def valid?(context = nil)
90b83222 Ohad Levy
super
30ae12bf Ohad Levy
orchestration_errors?
90b83222 Ohad Levy
end

# we override the destroy method, in order to ensure our queue exists before other callbacks
# and to process the queue only if we found no errors
def destroy
set_queue
super
end

private
173724cc Ohad Levy
e85584bf Paul Kelly
def proxy_error e
173724cc Ohad Levy
e.respond_to?(:message) ? e.message : e
e85584bf Paul Kelly
end
90b83222 Ohad Levy
# Handles the actual queue
# takes care for running the tasks in order
# if any of them fail, it rollbacks all completed tasks
# in order not to keep any left overs in our proxies.
0363ab42 Amos Benari
def process queue_name
90b83222 Ohad Levy
return true if Rails.env == "test"
# queue is empty - nothing to do.
0363ab42 Amos Benari
q = send(queue_name)
90b83222 Ohad Levy
return if q.empty?

# process all pending tasks
q.pending.each do |task|
# if we have failures, we don't want to process any more tasks
c7c5d840 Paul Kelly
break unless q.failed.empty?

90b83222 Ohad Levy
task.status = "running"
dd42df0a Ohad Levy
0363ab42 Amos Benari
update_cache
90b83222 Ohad Levy
begin
task.status = execute({:action => task.action}) ? "completed" : "failed"

a6f4f5f7 Ohad Levy
rescue Net::Conflict => e
task.status = "conflict"
@record_conflicts << e
30ae12bf Ohad Levy
failure e.message, nil, :conflict
#TODO: This is not a real error, but at the moment the proxy / foreman lacks better handling
# of the error instead of explode.
rescue Net::LeaseConflict => e
task.status = "failed"
failure "DHCP has a lease at #{e}"
c7c5d840 Paul Kelly
rescue RestClient::Exception => e
task.status = "failed"
26e2d30d Amos Benari
failure "#{task.name} task failed with the following error: #{proxy_error e}"
90b83222 Ohad Levy
rescue => e
task.status = "failed"
f28a6895 Ohad Levy
failure "#{task.name} task failed with the following error: #{e}"
90b83222 Ohad Levy
end
end

0363ab42 Amos Benari
update_cache
90b83222 Ohad Levy
# if we have no failures - we are done
30ae12bf Ohad Levy
return true if q.failed.empty? and q.pending.empty? and q.conflict.empty? and orchestration_errors?
90b83222 Ohad Levy
f28a6895 Ohad Levy
logger.warn "Rolling back due to a problem: #{q.failed + q.conflict}"
dd42df0a Ohad Levy
q.pending.each{ |task| task.status = "canceled" }

90b83222 Ohad Levy
# handle errors
# we try to undo all completed operations and trigger a DB rollback
102a8035 Paul Kelly
(q.completed + q.running).sort.reverse_each do |task|
90b83222 Ohad Levy
begin
task.status = "rollbacked"
0363ab42 Amos Benari
update_cache
90b83222 Ohad Levy
execute({:action => task.action, :rollback => true})
rescue => e
# if the operation failed, we can just report upon it
a6f4f5f7 Ohad Levy
failure "Failed to perform rollback on #{task.name} - #{e}"
90b83222 Ohad Levy
end
end

rollback
end

def execute opts = {}
obj, met = opts[:action]
rollback = opts[:rollback] || false
# at the moment, rollback are expected to replace set with del in the method name
if rollback
met = met.to_s
case met
when /set/
met.gsub!("set","del")
when /del/
met.gsub!("del","set")
else
raise "Dont know how to rollback #{met}"
end
met = met.to_sym
end
if obj.respond_to?(met)
return obj.send(met)
else
a6f4f5f7 Ohad Levy
failure "invalid method #{met}"
90b83222 Ohad Levy
raise "invalid method #{met}"
end
end

def set_queue
6454273f Paul Kelly
@queue = Orchestration::Queue.new
c7540a9c Amos Benari
@post_queue = Orchestration::Queue.new
a6f4f5f7 Ohad Levy
@record_conflicts = []
90b83222 Ohad Levy
end

# we keep the before update host object in order to compare changes
def setup_clone
return if new_record?
ff61accf Ohad Levy
@old = dup
90b83222 Ohad Levy
for key in (changed_attributes.keys - ["updated_at"])
@old.send "#{key}=", changed_attributes[key]
672f931d Paul Kelly
# At this point the old cached bindings may still be present so we force an AR association reload
# This logic may not work or be required if we switch to Rails 3
if (match = key.match(/^(.*)_id$/))
name = match[1].to_sym
next if name == :owner # This does not work for the owner association even from the console
self.send(name, true) if (send(name) and send(name).id != @attributes[key])
old.send(name, true) if (old.send(name) and old.send(name).id != old.attributes[key])
end
90b83222 Ohad Levy
end
end
30ae12bf Ohad Levy
def orchestration_errors?
overwrite? ? errors.are_all_conflicts? : errors.empty?
end

0363ab42 Amos Benari
def update_cache
ff217463 Amos Benari
Rails.cache.write(progress_report_id, (queue.all + post_queue.all).to_json, :expires_in => 5.minutes)
0363ab42 Amos Benari
end

90b83222 Ohad Levy
end
end