diff --git a/include/LifeCycleManager.h b/include/LifeCycleManager.h index 40999420de..ab6304a47e 100644 --- a/include/LifeCycleManager.h +++ b/include/LifeCycleManager.h @@ -53,7 +53,8 @@ public: CANCEL_FAILURE, /**< Sent by the VMM when a cancel action fails */ MONITOR_FAILURE, /**< Sent by the VMM when a VM has failed while active */ MONITOR_SUSPEND, /**< Sent by the VMM when a VM is paused while active */ - MONITOR_DONE, /**< Sent by the VMM when a VM is not found */ + MONITOR_DONE, /**< Sent by the VMM when a Host cannot be monitored*/ + MONITOR_POWEROFF, /**< Sent by the VMM when a VM is not found */ PROLOG_SUCCESS, /**< Sent by the TM when the prolog phase succeeds */ PROLOG_FAILURE, /**< Sent by the TM when the prolog phase fails */ EPILOG_SUCCESS, /**< Sent by the TM when the epilog phase succeeds */ @@ -197,6 +198,8 @@ private: void monitor_done_action(int vid); + void monitor_poweroff_action(int vid); + void prolog_success_action(int vid); void prolog_failure_action(int vid); diff --git a/src/im/MonitorThread.cc b/src/im/MonitorThread.cc index 921133fcbf..38c6a82744 100644 --- a/src/im/MonitorThread.cc +++ b/src/im/MonitorThread.cc @@ -73,7 +73,7 @@ void MonitorThread::do_message() } // ------------------------------------------------------------------------- - // Monitoring Error + // Monitoring Error. VMs running on the host are moved to UNKNOWN // ------------------------------------------------------------------------- if (result != "SUCCESS") { @@ -211,6 +211,10 @@ void MonitorThread::do_message() host->unlock(); + //-------------------------------------------------------------------------- + // Process VM information if any. VMs not reported by the hypervisor are + // moved to the POWEROFF state. + //-------------------------------------------------------------------------- if (vm_poll) { set::iterator its; @@ -218,7 +222,7 @@ void MonitorThread::do_message() for (its = lost.begin(); its != lost.end(); its++) { - lcm->trigger(LifeCycleManager::MONITOR_DONE, *its); + lcm->trigger(LifeCycleManager::MONITOR_POWEROFF, *its); } for (itm = found.begin(); itm != found.end(); itm++) diff --git a/src/lcm/LifeCycleManager.cc b/src/lcm/LifeCycleManager.cc index 62f79d37b1..c3bcb10206 100644 --- a/src/lcm/LifeCycleManager.cc +++ b/src/lcm/LifeCycleManager.cc @@ -113,6 +113,10 @@ void LifeCycleManager::trigger(Actions action, int _vid) aname = "MONITOR_DONE"; break; + case MONITOR_POWEROFF: + aname = "MONITOR_POWEROFF"; + break; + case PROLOG_SUCCESS: aname = "PROLOG_SUCCESS"; break; @@ -334,6 +338,10 @@ void LifeCycleManager::do_action(const string &action, void * arg) { monitor_done_action(vid); } + else if (action == "MONITOR_POWEROFF") + { + monitor_poweroff_action(vid); + } else if (action == "PROLOG_SUCCESS") { prolog_success_action(vid); diff --git a/src/lcm/LifeCycleStates.cc b/src/lcm/LifeCycleStates.cc index 49a296fad0..b34e37714f 100644 --- a/src/lcm/LifeCycleStates.cc +++ b/src/lcm/LifeCycleStates.cc @@ -1058,10 +1058,10 @@ void LifeCycleManager::cancel_success_action(int vid) //---------------------------------------------------- // POWEROFF STATE //---------------------------------------------------- + map empty; vm->delete_snapshots(); - map empty; vm->update_info(0, 0, -1, -1, empty); vmpool->update(vm); @@ -1263,6 +1263,59 @@ void LifeCycleManager::monitor_done_action(int vid) vm->unlock(); } +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + +void LifeCycleManager::monitor_poweroff_action(int vid) +{ + VirtualMachine * vm; + + vm = vmpool->get(vid,true); + + if ( vm == 0 ) + { + return; + } + + //This event should be ignored if the VM is not RUNNING + if ( vm->get_lcm_state() == VirtualMachine::RUNNING ) + { + //---------------------------------------------------- + // POWEROFF STATE + //---------------------------------------------------- + map empty; + time_t the_time = time(0); + + Nebula& nd = Nebula::instance(); + DispatchManager * dm = nd.get_dm(); + + vm->delete_snapshots(); + + vm->update_info(0, 0, -1, -1, empty); + + vm->set_resched(false); + + vm->set_state(VirtualMachine::SHUTDOWN_POWEROFF); + + vmpool->update(vm); + + vm->set_running_etime(the_time); + + vm->set_etime(the_time); + + vm->set_vm_info(); + + vm->set_reason(History::USER); + + vmpool->update_history(vm); + + //---------------------------------------------------- + + dm->trigger(DispatchManager::POWEROFF_SUCCESS,vid); + } + + vm->unlock(); +} /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ diff --git a/src/vmm_mad/remotes/poll_xen_kvm.rb b/src/vmm_mad/remotes/poll_xen_kvm.rb index 5f8d6b1b03..cd35f4fb23 100755 --- a/src/vmm_mad/remotes/poll_xen_kvm.rb +++ b/src/vmm_mad/remotes/poll_xen_kvm.rb @@ -21,7 +21,13 @@ require 'rexml/document' ENV['LANG']='C' +################################################################################ +# +# KVM Monitor Module +# +################################################################################ module KVM + # Constants for KVM operations CONF={ :dominfo => 'virsh --connect LIBVIRT_URI --readonly dominfo', :list => 'virsh --connect LIBVIRT_URI --readonly list', @@ -31,118 +37,121 @@ module KVM 'LIBVIRT_URI' => 'qemu:///system' } - def self.get_all_vm_info - vms=get_vm_info - - info={} - vms.each do |name, vm| - info[name]=vm[:values] - end - - info + # Execute a virsh command using the predefined command strings and URI + # @param command [Symbol] as defined in the module CONF constant + def self.virsh(command) + CONF[command].gsub('LIBVIRT_URI', CONF['LIBVIRT_URI']) end - def self.get_vm_names + # Get the information of a single VM. In case of error the VM is reported + # as not found. + # @param vm_id [String] with the VM information + def self.get_vm_info(one_vm) + dominfo = dom_info(one_vm) + + return { :state => '-' } if !dominfo + + psinfo = process_info(dominfo['UUID']) + + vm = Hash.new + + vm[:name] = one_vm + vm[:pid] = psinfo[1] + + cpu = get_cpu_info({one_vm => vm}) + + resident_mem = psinfo[5].to_i + max_mem = dominfo['Max memory'].split(/\s+/).first.to_i + + values=Hash.new + + values[:state] = get_state(dominfo['State']) + values[:usedcpu] = cpu[vm[:pid]] if cpu[vm[:pid]] + values[:usedmemory] = [resident_mem, max_mem].max + + values.merge!(get_interface_statistics(one_vm)) + + return values + end + + # Gets the information of all VMs + # + # @return [Hash, nil] Hash with the VM information or nil in case of error + def self.get_all_vm_info + vms_info = Hash.new + vms = Hash.new + text=`#{virsh(:list)}` - return [] if $?.exitstatus != 0 + return nil if $?.exitstatus != 0 - lines=text.split(/\n/)[2..-1] + lines = text.split(/\n/)[2..-1] - lines.map do |line| + names = lines.map do |line| line.split(/\s+/).delete_if {|d| d.empty? }[1] end - end - def self.process_info(uuid) - ps=`ps auxwww | grep -- '-uuid #{uuid}' | grep -v grep` - ps.split(/\s+/) - end + return vms_info if names.length == 0 - def self.get_vm_info(one_vm=nil) - vms={} + names.each do |vm| + dominfo = dom_info(vm) - names=get_vm_names + if dominfo + psinfo = process_info(dominfo['UUID']) - if names.length!=0 - names.each do |vm| - dominfo=dom_info(vm) - if dominfo - psinfo=process_info(dominfo['UUID']) + info= Hash.new - info={} - info[:dominfo]=dominfo - info[:psinfo]=psinfo - info[:name]=vm - info[:pid]=psinfo[1] + info[:dominfo] = dominfo + info[:psinfo] = psinfo + info[:name] = vm + info[:pid] = psinfo[1] - vms[vm]=info - end - end - - cpu=get_cpu_info(vms) - - vms.each do |name, vm| - if one_vm - next if name!=one_vm - end - - c=cpu[vm[:pid]] - vm[:cpu]=c if c - - monitor=Hash.new - - ps_data=vm[:psinfo] - dominfo=vm[:dominfo] - monitor[:cpu]=vm[:cpu] - monitor[:resident_memory]=ps_data[5].to_i - monitor[:max_memory]=dominfo['Max memory'].split(/\s+/).first.to_i - - monitor[:memory]=[monitor[:resident_memory], monitor[:max_memory]].max - - state=dominfo['State'] - - - monitor[:state]=get_state(state) - monitor[:cpus]=dominfo['CPU(s)'] - - values=Hash.new - - values[:state]=monitor[:state] - values[:usedcpu]=monitor[:cpu] - values[:usedmemory]=monitor[:memory] - - values.merge!(get_interface_statistics(name)) - - vm[:values]=values + vms[vm]=info end end - if one_vm - if vms[one_vm] - vms[one_vm][:values] - else - { :state => '-' } - end - else - vms + cpu = get_cpu_info(vms) + + vms.each do |name, vm| + ps_data = vm[:psinfo] + dominfo = vm[:dominfo] + + resident_mem = ps_data[5].to_i + max_mem = dominfo['Max memory'].split(/\s+/).first.to_i + + values = Hash.new + + values[:state] = get_state(dominfo['State']) + values[:usedcpu] = cpu[vm[:pid]] if cpu[vm[:pid]] + values[:usedmemory] = [resident_mem, max_mem].max + + values.merge!(get_interface_statistics(name)) + + vms_info[vm[:name]] = values end + + return vms_info end + # Gathers process information from a set of VMs. + # @param vms [Hash] of vms indexed by name. Value is a hash with :pid + # @return [Hash] with ps information def self.get_cpu_info(vms) - pids=vms.map {|name, vm| vm[:pid] } + pids = vms.map {|name, vm| vm[:pid] } pids.compact! - cpu={} + cpu = Hash.new pids.each_slice(20) do |slice| - data=%x{#{CONF[:top]} #{slice.join(',')}} + data = %x{#{CONF[:top]} #{slice.join(',')}} - lines=data.strip.split("\n") - block_size=lines.length/2 - valid_lines=lines.last(block_size) + lines = data.strip.split("\n") + + block_size = lines.length/2 + valid_lines = lines.last(block_size) first_domain = 7 + valid_lines.each_with_index{ |l,i| if l.match 'PID USER' first_domain=i+1 @@ -150,149 +159,196 @@ module KVM end } - domain_lines=valid_lines[first_domain..-1] + domain_lines = valid_lines[first_domain..-1] domain_lines.each do |line| - d=line.split - - cpu[d[0]]=d[8] + d = line.split + cpu[d[0]] = d[8] end end cpu end + # Process information for a KVM domain by its UUID + # @param uid [String] with user id + # @return [Array] of user processes + def self.process_info(uuid) + ps=`ps auxwww | grep -- '-uuid #{uuid}' | grep -v grep` + ps.split(/\s+/) + end + + # Gets the info of a domain by its id + # @param the ID of the VM as defined in libvirt + # @return [Hash] with the output of virsh dominfo, indexed by name (Id...) + # Example execution of dominfo + # Id: 5 + # Name: one-6 + # UUID: 06bc1876-fc6a-4dca-b41d-d7f2093b6b59 + # OS Type: hvm + # State: running + # CPU(s): 1 + # CPU time: 11.1s + # Max memory: 524288 KiB + # Used memory: 524288 KiB + # Persistent: no + # Autostart: disable + # Managed save: no + # Security model: none + # Security DOI: 0 def self.dom_info(vmid) - text=`#{virsh(:dominfo)} #{vmid}` + text = `#{virsh(:dominfo)} #{vmid}` return nil if $?.exitstatus != 0 - lines=text.split(/\n/) + lines = text.split(/\n/) + hash = Hash.new - hash=Hash.new + lines.map do |line| + parts = line.split(/:\s+/) - data=lines.map do |line| - parts=line.split(/:\s+/) - hash[parts[0]]=parts[1] + hash[parts[0]] = parts[1] end hash end - def self.virsh(command) - CONF[command].gsub('LIBVIRT_URI', CONF['LIBVIRT_URI']) - end + # Aggregate statics of all VM NICs + # @param the ID of the VM as defined in libvirt + # @return [Hash] with network stats, by name [symbol] :netrx, :nettx + def self.get_interface_statistics(vmid) + text = `#{virsh(:dumpxml)} #{vmid}` - def self.get_interface_names(vmid) - text=`#{virsh(:dumpxml)} #{vmid}` + return {} if $?.exitstatus != 0 + + doc = REXML::Document.new(text) + + interfaces = Array.new - doc=REXML::Document.new(text) - interfaces = [] doc.elements.each('domain/devices/interface/target') do |ele| interfaces << ele.attributes["dev"] end - interfaces - end + return {} if interfaces.empty? - def self.get_interface_statistics(vmid) - interfaces=get_interface_names(vmid) + values = Hash.new - if interfaces && !interfaces.empty? - values={} - values[:netrx]=0 - values[:nettx]=0 + values[:netrx] = 0 + values[:nettx] = 0 - interfaces.each do |interface| - text=`#{virsh(:domifstat)} #{vmid} #{interface}` + interfaces.each do |interface| + text=`#{virsh(:domifstat)} #{vmid} #{interface}` - text.each_line do |line| - columns=line.split(/\s+/) - case columns[1] + next if $?.exitstatus != 0 + + text.each_line do |line| + columns = line.split(/\s+/) + + case columns[1] when 'rx_bytes' - values[:netrx]+=columns[2].to_i + values[:netrx] += columns[2].to_i when 'tx_bytes' values[:nettx]+=columns[2].to_i end end - end - - values - else - {} end + + values end + # Translate libvirt state to Opennebula monitor state + # @param state [String] libvirt state + # @return [String] OpenNebula state def self.get_state(state) case state.gsub('-', '') - when *%w{running blocked shutdown dying idle} - 'a' - when 'paused' - 'd' - when 'crashed' - 'e' - else - '-' + when *%w{running blocked shutdown dying idle} + 'a' + when 'paused' + 'd' + when 'crashed' + 'e' + else + '-' end end end +################################################################################ +# +# Xen Monitor Module +# +################################################################################ module XEN + # Default configuration variables. It can be overridden through xenrc CONF={ 'XM_POLL' => 'sudo /usr/sbin/xentop -bi2' } + # Get the information of a single VM. In case of error the VM is reported + # as not found. + # @param vm_id [String] with the VM information def self.get_vm_info(vm_id) - data = get_all_vm_info[vm_id] + data = get_all_vm_info if !data return {:STATE => 'd'} else - return data + return data[vm_id] end end + # Gets the information of all VMs + # + # @return [Hash, nil] Hash with the VM information or nil in case of error def self.get_all_vm_info - begin - text=`#{CONF['XM_POLL']}` - lines=text.strip.split("\n") - block_size=lines.length/2 - valid_lines=lines.last(block_size) + begin + text = `#{CONF['XM_POLL']}` - first_domain = 4 - valid_lines.each_with_index{ |l,i| - if l.match 'NAME STATE' - first_domain=i+1 - break + return nil if $?.exitstatus != 0 + + lines = text.strip.split("\n") + + block_size = lines.length/2 + valid_lines = lines.last(block_size) + + first_domain = 4 + + valid_lines.each_with_index{ |l,i| + if l.match 'NAME STATE' + first_domain=i+1 + break + end + } + + domain_lines = valid_lines[first_domain..-1] + + domains = Hash.new + + domain_lines.each do |dom| + dom_data = dom.gsub('no limit', 'no-limit').strip.split + + dom_hash = Hash.new + + dom_hash[:name] = dom_data[0] + dom_hash[:state] = get_state(dom_data[1]) + dom_hash[:usedcpu] = dom_data[3] + dom_hash[:usedmemory] = dom_data[4] + dom_hash[:nettx] = dom_data[10].to_i * 1024 + dom_hash[:netrx] = dom_data[11].to_i * 1024 + + domains[dom_hash[:name]] = dom_hash end - } - domain_lines=valid_lines[first_domain..-1] - - domains=Hash.new - - domain_lines.each do |dom| - dom_data=dom.gsub('no limit', 'no-limit').strip.split - - dom_hash=Hash.new - - dom_hash[:name]=dom_data[0] - dom_hash[:state]=get_state(dom_data[1]) - dom_hash[:usedcpu]=dom_data[3] - dom_hash[:usedmemory]=dom_data[4] - dom_hash[:nettx]=dom_data[10].to_i * 1024 - dom_hash[:netrx]=dom_data[11].to_i * 1024 - - domains[dom_hash[:name]]=dom_hash + domains + rescue + STDERR.puts "Error executing #{CONF['XM_POLL']}" + nil end - - domains - rescue - STDERR.puts "Error executing #{CONF['XM_POLL']}" - nil - end end + # Returns an OpenNebula state from the Xen status + # @param state [String] with the Xen status + # @return [String] OpenNebula monitor state def self.get_state(state) case state.gsub('-', '')[-1..-1] when *%w{r b s d} @@ -307,63 +363,71 @@ module XEN end end +################################################################################ +# Functions to interface hypervisor information +################################################################################ -def select_hypervisor - hypervisor=nil - params=ARGV.clone +# Selects the hypervisor to be used based on the arguments or probe location +# This function also loads the associated configuration variables. +# @return [Module] with the hypervisor XEN, KVM +def setup_hypervisor + hypervisor = nil + params = ARGV.clone params.each_with_index do |param, index| case param - when '--kvm' - hypervisor=KVM - ARGV.delete_at(index) - when '--xen' - hypervisor=XEN - ARGV.delete_at(index) + when '--kvm' + hypervisor = KVM + ARGV.delete_at(index) + when '--xen' + hypervisor = XEN + ARGV.delete_at(index) end end if !hypervisor case $0 - when %r{/vmm\/kvm/} - hypervisor=KVM - when %r{/vmm\/xen\d?/} - hypervisor=XEN + when %r{/vmm\/kvm/} + hypervisor=KVM + when %r{/vmm\/xen\d?/} + hypervisor=XEN end end - hypervisor -end - -def load_vars(hypervisor) case hypervisor.name - when 'XEN' - file='xenrc' - vars=%w{XM_POLL} - when 'KVM' - file='kvmrc' - vars=%w{LIBVIRT_URI} - else - return + when 'XEN' + file = 'xenrc' + vars = %w{XM_POLL} + when 'KVM' + file = 'kvmrc' + vars = %w{LIBVIRT_URI} + else + return nil end -begin - env=`. #{File.dirname($0)+"/#{file}"};env` + # Load the rc variables and override the default values + begin + env = `. #{File.dirname($0)+"/#{file}"};env` + lines = env.split("\n") - lines=env.split("\n") - vars.each do |var| - lines.each do |line| - if a=line.match(/^(#{var})=(.*)$/) - hypervisor::CONF[var]=a[2] - break + vars.each do |var| + lines.each do |line| + if a = line.match(/^(#{var})=(.*)$/) + hypervisor::CONF[var] = a[2] + break + end end end + rescue end -rescue -end + + return hypervisor end - +# Returns an OpenNebula monitor string +# @param name [String] of the monitor metric +# @param value [String] of the monitor metric +# @return [String, nil] def print_data(name, value) if value "#{name.to_s.upcase}=#{value}" @@ -372,16 +436,16 @@ def print_data(name, value) end end - - +# Puts to STDOUT a string in the form "VAL1=VAR1 VAL2=VAR2" with the monitor +# attributes of the VM +# @param hypervisor [Module] +# @param vm_id [String] with the VM ID def print_one_vm_info(hypervisor, vm_id) - info=hypervisor.get_vm_info(vm_id) + info = hypervisor.get_vm_info(vm_id) exit(-1) if !info - #info.merge!(get_interface_statistics(vm_id)) - - values=info.map do |key, value| + values = info.map do |key, value| print_data(key, value) end @@ -393,15 +457,20 @@ def print_all_vm_info(hypervisor) require 'base64' require 'zlib' - vms=hypervisor.get_all_vm_info + vms = hypervisor.get_all_vm_info + + return nil if vms.nil? + + compressed = Zlib::Deflate.deflate(vms.to_yaml) - compressed=Zlib::Deflate.deflate(vms.to_yaml) puts Base64.encode64(compressed).delete("\n") end def print_all_vm_template(hypervisor) vms=hypervisor.get_all_vm_info + return nil if vms.nil? + puts "VM_POLL=YES" vms.each do |name, data| @@ -411,34 +480,37 @@ def print_all_vm_template(hypervisor) number = name.split('-').last end - string="VM=[\n" - string<<" ID=#{number},\n" - string<<" DEPLOY_ID=#{name},\n" + string = "VM=[\n" + string << " ID=#{number},\n" + string << " DEPLOY_ID=#{name},\n" - values=data.map do |key, value| + values = data.map do |key, value| print_data(key, value) end - monitor=values.zip.join(' ') + monitor = values.zip.join(' ') - string<<" POLL=\"#{monitor}\" ]" + string << " POLL=\"#{monitor}\" ]" puts string end end -hypervisor=select_hypervisor + +################################################################################ +# MAIN PROGRAM +################################################################################ + +hypervisor = setup_hypervisor if !hypervisor STDERR.puts "Could not detect hypervisor" exit(-1) end -load_vars(hypervisor) +vm_id = ARGV[0] -vm_id=ARGV[0] - -if vm_id=='-t' +if vm_id == '-t' print_all_vm_template(hypervisor) elsif vm_id print_one_vm_info(hypervisor, vm_id)