require 'fission/leasesfile' require 'shellwords' require 'fission/error' module Fission class VM attr_reader :name def initialize(name) @name = name end ##################################################### # Path Helpers ##################################################### # Returns the topdir of the vm def path File.join Fission.config.attributes['vm_dir'], "#{@name}.vmwarevm" end # Returns a string to the path of the config file # There is no guarantee it exists def vmx_path return File.join(path, "#{@name}.vmx") end #################################################################### # State information #################################################################### def running? raise Fission::Error,"VM #{@name} does not exist" unless self.exists? command = "#{vmrun_cmd} list" output = `#{command}` response = Fission::Response.new :code => $?.exitstatus if response.successful? vms = output.split("\n").select do |vm| vm.include?('.vmx') && File.exists?(vm) && File.extname(vm) == '.vmx' end return vms.include?(self.vmx_path) else raise Fission::Error,"Error listing the state of vm #{@name}:\n#{output}" end end def suspended? raise Fission::Error,"VM #{@name} does not exist" unless self.exists? suspend_filename=File.join(File.dirname(vmx_path), File.basename(vmx_path,".vmx")+".vmem") return File.exists?(suspend_filename) end # Checks to see if a vm exists def exists? File.exists? vmx_path end # Returns the state of a vm def state return "not created" unless self.exists? return "suspend" if self.suspended? return "running" if self.running? return "not running" end #################################################################### # VM information #################################################################### # Returns an Array of snapshot names def snapshots raise Fission::Error,"VM #{@name} does not exist" unless self.exists? command = "#{vmrun_cmd} listSnapshots #{vmx_path.shellescape} 2>&1" output = `#{command}` raise "There was an error listing the snapshots of #{@name} :\n #{output}" unless $?.exitstatus==0 snaps_unfiltered = output.split("\n").select { |s| !s.include? 'Total snapshots:' } snaps=snaps_unfiltered.map { |s| s.strip } return snaps end # Retrieve the first mac address for a vm # This will only retrieve the first auto generate mac address def mac_address raise ::Fission::Error,"VM #{@name} does not exist" unless self.exists? line=File.new(vmx_path).grep(/^ethernet0.generatedAddress =/) if line.nil? #Fission.ui.output "Hmm, the vmx file #{vmx_path} does not contain a generated mac address " return nil end address=line.first.split("=")[1].strip.split(/\"/)[1] return address end # Retrieve the ip address for a vm. # This will only look for dynamically assigned ip address via vmware dhcp def ip_address raise ::Fission::Error,"VM #{@name} does not exist" unless self.exists? unless mac_address.nil? lease=LeasesFile.new("/var/db/vmware/vmnet-dhcpd-vmnet8.leases").find_lease_by_mac(mac_address) if lease.nil? return nil else return lease.ip end else # No mac address was found for this machine so we can't calculate the ip-address return nil end end #################################################################### # VMS information #################################################################### # Returns an array of vm objects def self.all vm_dirs = Dir[File.join Fission.config.attributes['vm_dir'], '*.vmwarevm'].select do |d| File.directory? d end vm_names=vm_dirs.map { |d| File.basename d, '.vmwarevm' } vms=[] vm_names.each do |vmname| vm=Fission::VM.new vmname vms << vm end return vms end # Returns an array of vms that are running def self.all_running running_vms=self.all.select do |vm| vm.state=="running" end return running_vms end # Returns an existing vm def self.get(name) return Fission::VM.new(name) end ##################################################### # VM Class Actions ##################################################### def self.clone(source_vm, target_vm) raise Fission::Error,"VM #{source_vm} does not exist" unless Fission::VM.new(source_vm).exists? raise Fission::Error,"VM #{target_vm} already exists" if Fission::VM.new(target_vm).exists? FileUtils.cp_r Fission::VM.new(source_vm).path, Fission::VM.new(target_vm).path rename_vm_files source_vm, target_vm update_config source_vm, target_vm response = Response.new :code => 0 end def self.delete(vm_name) raise Fission::Error,"VM #{vm_name} does not exist" unless Fission::VM.new(vm_name).exists? vm=Fission::VM.new(vm_name) FileUtils.rm_rf vm.path Fission::Metadata.delete_vm_info(vm.path) Response.new :code => 0 end ##################################################### # VM Instance Actions ##################################################### def create_snapshot(name) raise Fission::Error,"VM #{@name} does not exist" unless self.exists? command = "#{vmrun_cmd} snapshot #{vmx_path.shellescape} \"#{name}\" 2>&1" output = `#{command}` response = Fission::Response.new :code => $?.exitstatus response.output = output unless response.successful? response end def start(args={}) raise Fission::Error,"VM #{@name} does not exist" unless self.exists? raise Fission::Error,"VM #{@name} is already started" if self.running? command = "#{vmrun_cmd} start #{vmx_path.shellescape}" if !args[:headless].blank? && args[:headless] command << " nogui 2>&1" else command << " gui 2>&1" end output = `#{command}` response = Fission::Response.new :code => $?.exitstatus response.output = output unless response.successful? response end def stop raise Fission::Error,"VM #{@name} does not exist" unless self.exists? raise Fission::Error,"VM #{@name} is not running" unless self.running? command = "#{vmrun_cmd} stop #{vmx_path.shellescape} 2>&1" output = `#{command}` response = Fission::Response.new :code => $?.exitstatus response.output = output unless response.successful? response end def halt raise Fission::Error,"VM #{@name} does not exist" unless self.exists? raise Fission::Error,"VM #{@name} is not running" unless self.running? command = "#{vmrun_cmd} stop #{vmx_path.shellescape} hard 2>&1" output = `#{command}` response = Fission::Response.new :code => $?.exitstatus response.output = output unless response.successful? response end def resume raise Fission::Error,"VM #{@name} does not exist" unless self.exists? raise Fission::Error,"VM #{@name} is already running" if self.running? if self.suspended? self.start end end def suspend raise Fission::Error,"VM #{@name} does not exist" unless self.exists? raise Fission::Error,"VM #{@name} is not running" unless self.running? command = "#{vmrun_cmd} suspend #{vmx_path.shellescape} hard 2>&1" output = `#{command}` response = Fission::Response.new :code => $?.exitstatus response.output = output unless response.successful? response end # Action to revert to a snapshot # Returns a response object def revert_to_snapshot(name) raise Fission::Error,"VM #{@name} does not exist" unless self.exists? command = "#{vmrun_cmd} revertToSnapshot #{vmx_path.shellescape} \"#{name}\" 2>&1" output = `#{command}` response = Fission::Response.new :code => $?.exitstatus response.output = output unless response.successful? response end ##################################################### # Helpers ##################################################### private def self.rename_vm_files(from, to) to_vm=Fission::VM.new(to) files_to_rename(from, to).each do |filename| text_to_replace = File.basename(filename, File.extname(filename)) if File.extname(filename) == '.vmdk' if filename.match /\-s\d+\.vmdk/ text_to_replace = filename.partition(/\-s\d+.vmdk/).first end end unless File.exists?(File.join(to_vm.path, filename.gsub(text_to_replace, to))) FileUtils.mv File.join(to_vm.path, filename), File.join(to_vm.path, filename.gsub(text_to_replace, to)) end end end def self.files_to_rename(from, to) files_which_match_source_vm = [] other_files = [] from_vm=Fission::VM.new(from) Dir.entries(from_vm.path).each do |f| unless f == '.' || f == '..' f.include?(from) ? files_which_match_source_vm << f : other_files << f end end files_which_match_source_vm + other_files end def self.vm_file_extensions ['.nvram', '.vmdk', '.vmem', '.vmsd', '.vmss', '.vmx', '.vmxf'] end # This is done after a clone has been done # All files are already at the to location # The content of the text files will be substituted with strings from => to def self.update_config(from, to) to_vm=Fission::VM.new(to) ['.vmx', '.vmxf', '.vmdk'].each do |ext| file = File.join to_vm.path, "#{to}#{ext}" unless File.binary?(file) text = (File.read file).gsub from, to # Force binary mode to prevent windows from putting CR-LF end line style # http://www.ruby-forum.com/topic/60697#58748 File.open(file, 'wb'){ |f| f.print text } end end # Rewrite vmx file to avoid messages new_vmx_file=File.open(File.join(to_vm.vmx_path),'r') content=new_vmx_file.read # Filter out other values content=content.gsub(/^tools.remindInstall.*\n/, "") content=content.gsub(/^uuid.action.*\n/,"").strip # Remove generate mac addresses content=content.gsub(/^ethernet.+generatedAddress.*\n/,"").strip # Add the correct values content=content+"\ntools.remindInstall = \"FALSE\"\n" content=content+"uuid.action = \"create\"\n" # Now rewrite the vmx file # Force binary mode to prevent windows from putting CR-LF end line style # http://www.ruby-forum.com/topic/60697#58748 File.open(new_vmx_file,'wb'){ |f| f.print content} end def vmrun_cmd if File.exists?("/Library/Application Support/VMware Fusion/vmrun") return("/Library/Application Support/VMware Fusion/vmrun").shellescape end if File.exists?("/Applications/VMware Fusion.app/Contents/Library/vmrun") return("/Applications/VMware Fusion.app/Contents/Library/vmrun").shellescape end end end end