diff --git a/src/mad/ruby/DriverExecHelper.rb b/src/mad/ruby/DriverExecHelper.rb index 37581568fd..d6b632b7e0 100644 --- a/src/mad/ruby/DriverExecHelper.rb +++ b/src/mad/ruby/DriverExecHelper.rb @@ -24,7 +24,7 @@ module DriverExecHelper :failure => "FAILURE" } - def failed?(rc_str) + def self.failed?(rc_str) return rc_str == RESULT[:failure] end diff --git a/src/vmm_mad/exec/one_vmm_exec.rb b/src/vmm_mad/exec/one_vmm_exec.rb index 6a6b021e73..6d1c9130ca 100755 --- a/src/vmm_mad/exec/one_vmm_exec.rb +++ b/src/vmm_mad/exec/one_vmm_exec.rb @@ -40,13 +40,20 @@ require 'pp' class VmmAction attr_reader :data + # Initialize a VmmAction object + # @param[OpenNebula::ExecDriver] Driver to be used for the actions + # @param[String] Id of the VM + # @param[String] name of the actions as described in the VMM protocol + # @param[xml_data] data sent from OpenNebula core def initialize(driver, id, action, xml_data) - @vmm=driver - @id=id - @main_action=action - @xml_data=@vmm.decode(xml_data) + # Initialize object with xml data + @vmm = driver + @id = id - @data=Hash.new + @main_action = action + @xml_data = @vmm.decode(xml_data) + + @data = Hash.new get_data(:host) get_data(:net_drv) @@ -76,6 +83,115 @@ class VmmAction end end + #Execute a set of steps defined with + # - :driver :vmm or :vnm to execute the step + # - :action for the step + # - :parameters command line paremeters for the action + # - :destination use next host + # - :fail_action steps to be executed if steps fail + # - :stdin for the action + # @param [Array] of steps + def run(steps, info_on_success = nil) + result = execute_steps(steps) + + #Prepare the info for the OpenNebula core + if DriverExecHelper.failed?(result) + info = @data[:failed_info] + else + info = @data["#{@main_action.to_s}_info".to_sym] + end + + @vmm.send_message(VirtualMachineDriver::ACTION[@main_action], + result, @id, info) + end + + private + + DRIVER_NAMES = { + :vmm => "virtualization driver" + :vnm => "network driver" + } + + # Executes a set of steps. If one step fails any recover action is performed + # and the step execution breaks. + # @param [Array] array of steps to be executed + # @return [String, Hash] "SUCCESS/FAILURE" for the step set, and + # information associated to each step (by :_info). In case of + # failure information is also in [:failed_info] + def execute_steps(steps) + steps.each do |step| + # Execute Step + case step[:driver] + when :vmm + if step[:destination] + host = @data[:dest_host] + ssh = @ssh_dst + else + host = @data[:host] + ssh = @ssh_src + end + + result, info = @vmm.do_action(get_parameters(step[:parameters]), + @id, + host, + step[:action], + :ssh_stream => ssh, + :respond => false, + :stdin => step[:stdin]) + when :vnm + if step[:destination] + vnm = @vnm_dst + else + vnm = @vnm_src + end + + result, info = vnm.do_action(@id, step[:action]) + else + result = DriverExecHelper.RESULT[:failure] + info = "No driver in #{step[:action]}"} + end + + # Save the step info + @data["#{step[:action]}_info".to_sym] = info + + # Roll back steps, store failed info and break steps + if DriverExecHelper.failed?(result) + execute_steps(@data[:fail_actions]) if @data[:fail_actions] + @data[:failed_info] = info + + @vmm.log(@id, + "Failed to execute #{DRIVER_NAMES[step[:driver]]} " \ + "operation: #{step[:action]}.") + break + else + @vmm.log(@id, + "Sussecfully execute #{DRIVER_NAMES[step[:driver]]} " \ + "operation: #{step[:action]}.") + end + end + + return result + end + + # Prepare the parameters for the action step generating a blanck separated + # list of command arguments + # @param [Hash] an action step + def get_parameters(step_params) + parameters = step_params || [] + + parameters.map do |param| + if Symbol===param + @data[param].to_s + else + param + end + end.join(' ') + end + + # Extracts data from the XML argument of the VMM action + # @param [Symbol] corresponding to a XML element + # @param [String] an xpath for the XML element + # @return [String] the element value def get_data(name, xml_path=nil) if xml_path path=xml_path.to_s @@ -85,94 +201,6 @@ class VmmAction @data[name]=@xml_data.elements[path].text end - -=begin - { - :driver => :vmm, - :action => :deploy, - :parameters => [:host, :deploy_id], - :destination => false - :save_info => :deploy_id, - :fail_actions => [], - :pre_log => "", - :post_log => "", - :stdin => nil - } -=end - - def execute_steps(steps) - result=false - info='Action #{step[:action]} not found' - - steps.each do |step| - - driver=step[:driver] || :vmm - - @vmm.log(@id, @data[:pre_log]) if @data[:pre_log] - - case driver - when :vmm - if step[:destination] - host = @data[:dest_host] - ssh = @ssh_dst - else - host = @data[:host] - ssh = @ssh_src - end - - result, info=@vmm.do_action(get_parameters(step), @id, host, - step[:action], - :ssh_stream => ssh, - :respond => false, - :stdin => step[:stdin]) - - when :vnm - if step[:destination] - vnm=@vnm_dst - else - vnm=@vnm_src - end - - result, info=vnm.do_action(@id, step[:action]) - else - end - - # Save the info variable if asked for - if @data[:save_info] - @data[@data[:save_info]]=info - end - - if @vmm.failed?(result) - if @data[:fail_actions] - execute_steps(@data[:fail_actions]) - end - - break - end - - @vmm.log(@id, @data[:post_log]) if @data[:post_log] - end - - return result, info - end - - def run(steps) - result, info=execute_steps(steps) - - @vmm.send_message(VirtualMachineDriver::ACTION[@main_action], - result, @id, info) - end - - def get_parameters(step) - parameters=step[:parameters] || [] - parameters.map do |param| - if Symbol===param - @data[param].to_s - else - param - end - end.join(' ') - end end @@ -180,7 +208,9 @@ end class ExecDriver < VirtualMachineDriver attr_reader :options - # SshDriver constructor + # Initializes the VMM driver + # @param [String] hypervisor name identifies the plugin + # @param [OpenNebulaDriver::options] def initialize(hypervisor, options={}) @options={ :threaded => true @@ -191,20 +221,26 @@ class ExecDriver < VirtualMachineDriver @hypervisor = hypervisor end + # Creates an SshStream to execute commands on the target host + # @param[String] the hostname of the host + # @param[String] id of the VM to log messages + # @return [SshStreamCommand] def get_ssh_stream(host, id) SshStreamCommand.new(host, @remote_scripts_base_path, log_method(id)) end + #--------------------------------------------------------------------------- + # Virtual Machine Manager Protocol Actions + #--------------------------------------------------------------------------- + # # DEPLOY action, sends the deployment file to remote host + # def deploy(id, drv_message) # ---------------------------------------------------------------------- # Initialization of deployment data # ---------------------------------------------------------------------- - - action=VmmAction.new(self, id, :deploy, drv_message) - local_dfile=action.data[:local_dfile] if !local_dfile || File.zero?(local_dfile) @@ -221,28 +257,33 @@ class ExecDriver < VirtualMachineDriver dfile = action.data[:remote_dfile] end + # ---------------------------------------------------------------------- + # Deployment Steps + # ---------------------------------------------------------------------- + action = VmmAction.new(self, id, :deploy, drv_message) + steps=[ + # Execute pre-boot networking setup { - :driver => :vnm, - :action => :pre, - :post_log => "Successfully executed network driver " << - "#{action.data[:net_drv]} (pre-boot)" + :driver => :vnm, + :action => :pre }, + # Boot the Virtual Machine { - :driver => :vmm, - :action => :deploy, - :parameters => [dfile, :host], - :stdin => domain, - :save_info => :deploy_id - }, + :driver => :vmm, + :action => :deploy, + :parameters => [dfile, :host], + :stdin => domain + }, + # Execute post-boot networking setup { - :driver => :vnm, - :action => :post, - :fail_actions => [ + :driver => :vnm, + :action => :post, + :fail_actions => [ { :driver => :vmm, :action => :cancel, - :parameters => [:deploy_id, :host] + :parameters => [:deploy_info, :host] } ] } @@ -251,187 +292,149 @@ class ExecDriver < VirtualMachineDriver action.run(steps) end - # Basic Domain Management Operations - + # + # SHUTDOWN action, graceful shutdown and network clean up + # def shutdown(id, drv_message) - action=VmmAction.new(self, id, :shutdown, drv_message) + action = VmmAction.new(self, id, :shutdown, drv_message) steps=[ + # Shutdown the Virtual Machine { :driver => :vmm, :action => :shutdown, - :parameters => [:deploy_id, :host], - :post_log => "Successfully shut down VM with id: " << - action.data[:deploy_id] + :parameters => [:deploy_id, :host] }, + # Execute networking clean up operations { - :driver => :vnm, - :action => :clean, - :post_log => "Successfully executed network driver " << - "#{action.data[:net_drv]} (clean-shutdown)" + :driver => :vnm, + :action => :clean } } action.run(steps) end + # + # CANCEL action, destroys a VM and network clean up + # def cancel(id, drv_message) - action=VmmAction.new(self, id, :cancel, drv_message) + action = VmmAction.new(self, id, :cancel, drv_message) steps=[ + # Cancel the Virtual Machine { :driver => :vmm, :action => :cancel, - :parameters => [:deploy_id, :host], - :post_log => "Successfully canceled VM with id: " << - action.data[:deploy_id] + :parameters => [:deploy_id, :host] }, + # Execute networking clean up operations { - :driver => :vnm, - :action => :clean, - :post_log => "Successfully executed network driver " << - "#{action.data[:net_drv]} (clean-cancel)" + :driver => :vnm, + :action => :clean } ] action.run(steps) end + # + # SAVE action, stops the VM and saves its state, network is cleaned up + # def save(id, drv_message) - action=VmmAction.new(self, id, :save, drv_message) + action = VmmAction.new(self, id, :save, drv_message) steps=[ + # Save the Virtual Machine state { :driver => :vmm, :action => :save, - :parameters => [:deploy_id, :checkpoint_file, :host], - :post_log => "Successfully saved VM with id: " << - action.data[:deploy_id] + :parameters => [:deploy_id, :checkpoint_file, :host] }, + # Execute networking clean up operations { - :driver => :vnm, - :action => :clean, - :post_log => "Successfully executed network driver " << - "#{action.data[:net_drv]} (clean-save)" + :driver => :vnm, + :action => :clean } ] action.run(steps) end + # + # RESTORE action, restore a VM from a previous state, and restores network + # def restore(id, drv_message) action=VmmAction.new(self, id, :restore, drv_message) steps=[ + # Execute pre-boot networking setup { :driver => :vnm, - :action => :pre, - :post_log => "Successfully executed network driver " << - "#{action.data[:net_drv]} (pre-restore)" + :action => :pre }, + # Restore the Virtual Machine from checkpoint { :driver => :vmm, :action => :restore, - :parameters => [:checkpoint_file, :host], - :save_info => :deploy_id, - :post_log => "Successfully restored VM with id: " << - action.data[:deploy_id] + :parameters => [:checkpoint_file, :host] }, + # Execute post-boot networking setup { - :driver => :vnm, - :action => :post, - :fail_actions => [ + :driver => :vnm, + :action => :post, + :fail_actions => [ { - :pre_log => "Failed to execute network driver" << - "#{action.data[:net_drv]} (post-restore). " << - "Cancelling VM with id: #{action.data[:deploy_id]}" :driver => :vmm, :action => :cancel, :parameters => [:deploy_id, :host] } ], - :post_log => "Successfully executed network driver " << - "#{action.data[:net_drv]} (post-restore)" } ] action.run(steps) end + # + # MIGRATE (live) action, migrates a VM to another host creating network + # def migrate(id, drv_message) - data = decode(drv_message) - net_drv = data.elements['NET_DRV'].text - host = data.elements['HOST'].text - deploy_id = data.elements['DEPLOY_ID'].text - dest_host = data.elements['MIGR_HOST'].text - dest_driver = data.elements['MIGR_NET_DRV'].text + action=VmmAction.new(self, id, :migrate, drv_message) - ssh_src = SshStreamCommand.new(host, - @remote_scripts_base_path, - log_method(id)) + steps=[ + # Execute pre-boot networking setup on migrating host + { + :driver => :vnm, + :action => :pre, + :destination => true + }, + # Migrate the Virtual Machine + { + :driver => :vmm, + :action => :migrate, + :parameters => [:deploy_id, :migr_host, :host] + }, + # Execute networking clean up operations + { + :driver => :vnm, + :action => :clean + }, + # Execute post-boot networking setup on migrating host + { + :driver => :vnm, + :action => :post, + :destination => :true + #TODO :fail_action what to do here? cancel VM? + }, + ] - ssh_dst = SshStreamCommand.new(dest_host, - @remote_scripts_base_path, - log_method(id)) - - vnm_src = VirtualNetworkDriver.new(net_drv, - :local_actions => @options[:local_actions], - :message => data, - :ssh_stream => ssh_src) - - vnm_dst = VirtualNetworkDriver.new(dest_driver, - :local_actions => @options[:local_actions], - :message => data, - :ssh_stream => ssh_dst) - - # ---------------------------------------------------------------------- - # Execute pre-boot action of the network driver - # ---------------------------------------------------------------------- - - result, info = vnm_dst.do_action(id, :pre) - - if failed?(result) - send_message(ACTION[:migrate], result, id,info) - return - end - - log(id, "Successfully executed network driver #{net_drv} (pre-migrate)") - - result, info = do_action("#{deploy_id} #{dest_host} #{host}", id, host, - :migrate, - :save, - :ssh_stream => ssh_src, - :respond => false) - if failed?(result) - send_message(ACTION[:migrate], result, id, info) - return - end - - log(id, "Successfully migrated VM with id: #{deploy_id}") - - # ---------------------------------------------------------------------- - # Execute post-migrate action of the network driver - # ---------------------------------------------------------------------- - - result, info = vnm_dst.do_action(id, :post) - - if failed?(result) - log(id, "Failed to executed network driver #{dest_driver} (post-migrate)") - log(id, "Canceling VM with id: #{deploy_id}") - - do_action("#{deploy_id} #{host}", id, dest_host, :cancel, - :ssh_stream => ssh_dst, - :respond => false) - - send_message(ACTION[:restore], result, id, info) - return - end - - log(id, "Successfully executed network driver #{net_drv} (post-restore)") - - send_message(ACTION[:restore], RESULT[:success], id, deploy_id) + action.run(steps) end + # + # POLL action, gets information of a VM + # def poll(id, drv_message) data = decode(drv_message) host = data.elements['HOST'].text @@ -441,7 +444,12 @@ class ExecDriver < VirtualMachineDriver end end -# SshDriver Main program +################################################################################ +# +# Virtual Machine Manager Execution Driver - Main Program +# +################################################################################ + opts = GetoptLong.new( [ '--retries', '-r', GetoptLong::OPTIONAL_ARGUMENT ], [ '--threads', '-t', GetoptLong::OPTIONAL_ARGUMENT ],