lib/esx.rb in esx-0.3.2 vs lib/esx.rb in esx-0.4.1

- old
+ new

@@ -4,20 +4,32 @@ require 'net/scp' require 'net/ssh' module ESX - VERSION = '0.3.2' + VERSION = '0.4.1' + + if !defined? Log or Log.nil? + Log = Logger.new($stdout) + Log.formatter = proc do |severity, datetime, progname, msg| + "[ESX] #{severity}: #{msg}\n" + end + Log.level = Logger::INFO unless (ENV["DEBUG"].eql? "yes" or \ + ENV["DEBUG"].eql? "true") + Log.debug "Initializing logger" + end class Host attr_reader :address, :user, :password + attr_accessor :templates_dir - def initialize(address, user, password) + def initialize(address, user, password, opts = {}) @address = address @password = password @user = user + @templates_dir = opts[:templates_dir] || "/vmfs/volumes/datastore1/esx-gem/templates" end # Connect to a ESX host # # Requires hostname/ip, username and password @@ -214,50 +226,148 @@ # # Run a command in the ESX host via SSH # def remote_command(cmd) + output = "" Net::SSH.start(@address, @user, :password => @password) do |ssh| - ssh.exec! cmd + output = ssh.exec! cmd end + output end # # Upload file # - def upload_file(source, dest, print_progress = true) + def upload_file(source, dest, print_progress = false) Net::SSH.start(@address, @user, :password => @password) do |ssh| - puts "Uploading file... (#{File.basename(source)})" + Log.info "Uploading file #{File.basename(source)}..." if print_progress ssh.scp.upload!(source, dest) do |ch, name, sent, total| if print_progress print "\rProgress: #{(sent.to_f * 100 / total.to_f).to_i}% completed" end end end - puts if print_progress end - def import_disk(source, destination, print_progress = true) + def template_exist?(vmdk_file) + template_file = File.join(@templates_dir, File.basename(vmdk_file)) + Log.debug "Checking if template #{template_file} exists" + Net::SSH.start(@address, @user, :password => @password) do |ssh| + return false if (ssh.exec! "ls -la #{@templates_dir} 2>/dev/null").nil? + return false if (ssh.exec! "ls #{template_file} 2>/dev/null").nil? + end + Log.debug "Template #{template_file} found" + true + end + alias :has_template? :template_exist? + + def list_templates + templates = [] + Net::SSH.start(@address, @user, :password => @password) do |ssh| + output = (ssh.exec! "ls -l #{@templates_dir}/*-flat.vmdk 2>/dev/null") + output.each_line do |t| + templates << t.gsub(/-flat\.vmdk/,".vmdk").split().last.strip.chomp rescue next + end unless output.nil? + end + templates + end + + # + # Expects fooimg.vmdk + # Trims path if /path/to/fooimg.vmdk + # + def delete_template(template_disk) + Log.debug "deleting template #{template_disk}" + template = File.join(@templates_dir, File.basename(template_disk)) + template_flat = File.join(@templates_dir, File.basename(template_disk, ".vmdk") + "-flat.vmdk") + Net::SSH.start(@address, @user, :password => @password) do |ssh| + if (ssh.exec! "ls #{template} 2>/dev/null").nil? + Log.error "Template #{template_disk} does not exist" + raise "Template does not exist" + end + ssh.exec!("rm -f #{template} && rm -f #{template_flat} 2>&1") + end + end + + def import_template(source, params = {}) + print_progress = params[:print_progress] || false + dest_file = File.join(@templates_dir, File.basename(source)) + Log.debug "Importing template #{source} to #{dest_file}" + return dest_file if template_exist?(dest_file) + Net::SSH.start(@address, @user, :password => @password) do |ssh| + if (ssh.exec! "ls -la #{@templates_dir} 2>/dev/null").nil? + # Create template dir + Log.debug "Creating templates dir #{@templates_dir}" + ssh.exec "mkdir -p #{@templates_dir}" + end + end + import_disk_convert(source, dest_file, print_progress) + end + + # + # Expects vmdk source file path and destination path + # + # copy_from_template "/home/fooser/my.vmdk", "/vmfs/volumes/datastore1/foovm/foovm.vmdk" + # + # Destination directory must exist otherwise rises exception + # + def copy_from_template(template_disk, destination) + Log.debug "Copying from template #{template_disk} to #{destination}" + raise "Template does not exist" if not template_exist?(template_disk) + source = File.join(@templates_dir, File.basename(template_disk)) + Net::SSH.start(@address, @user, :password => @password) do |ssh| + Log.debug "Clone disk #{source} to #{destination}" + Log.debug ssh.exec!("vmkfstools -i #{source} --diskformat thin #{destination} 2>&1") + end + end + + # + # Imports a VMDK + # + # if params has :use_template => true, the disk is saved as a template in + # @templates_dir and cloned from there. + # + # Destination directory must exist otherwise rises exception + # + def import_disk(source, destination, print_progress = false, params = {}) + use_template = params[:use_template] || false + if use_template + Log.debug "import_disk :use_template => true" + if !template_exist?(source) + Log.debug "import_disk, template does not exist, importing." + import_template(source, { :print_progress => print_progress }) + end + copy_from_template(source, destination) + else + import_disk_convert source, destination, print_progress + end + end + + # + # This method does all the heavy lifting when importing the disk. + # It also converts the imported VMDK to thin format + # + def import_disk_convert(source, destination, print_progress = false) tmp_dest = destination + ".tmp" Net::SSH.start(@address, @user, :password => @password) do |ssh| if not (ssh.exec! "ls #{destination} 2>/dev/null").nil? raise Exception.new("Destination file #{destination} already exists") end - puts "Uploading file... (#{File.basename(source)})" + Log.info "Uploading file... (#{File.basename(source)})" if print_progress ssh.scp.upload!(source, tmp_dest) do |ch, name, sent, total| if print_progress print "\rProgress: #{(sent.to_f * 100 / total.to_f).to_i}%" end end if print_progress - puts "\nConverting disk..." + Log.info "Converting disk..." ssh.exec "vmkfstools -i #{tmp_dest} --diskformat thin #{destination}; rm -f #{tmp_dest}" else ssh.exec "vmkfstools -i #{tmp_dest} --diskformat thin #{destination} >/dev/null 2>&1; rm -f #{tmp_dest}" end end - puts end private # # disk_file @@ -315,10 +425,12 @@ # **This method should never be called manually.** # def self.wrap(vm) _vm = VM.new _vm.name = vm.name - _vm.memory_size = vm.summary.config.memorySizeMB*1024*1024 + ## HACK: for some reason vm.summary.config.memorySizeMB returns nil + # under some conditions + _vm.memory_size = vm.summary.config.memorySizeMB*1024*1024 rescue 0 _vm.cpus = vm.summary.config.numCpu _vm.ethernet_cards_number = vm.summary.config.numEthernetCards _vm.virtual_disks_number = vm.summary.config.numVirtualDisks _vm.vm_object = vm _vm