lib/esx.rb in esx-0.1.1 vs lib/esx.rb in esx-0.2
- old
+ new
@@ -1,22 +1,33 @@
require 'rubygems'
require 'rbvmomi'
require 'alchemist'
+require 'net/scp'
+require 'net/ssh'
module ESX
- VERSION = '0.1.1'
+ VERSION = '0.2'
+
class Host
+ attr_reader :address, :user, :password
+
+ def initialize(address, user, password)
+ @address = address
+ @password = password
+ @user = user
+ end
+
# Connect to a ESX host
#
# Requires hostname/ip, username and password
#
# Host connection is insecure by default
def self.connect(host, user, password, insecure=true)
vim = RbVmomi::VIM.connect :host => host, :user => user, :password => password, :insecure => insecure
- host = Host.new
+ host = Host.new(host, user,password)
host.vim = vim
host
end
def vim=(vim)
@@ -75,10 +86,12 @@
# :cpus => 1, #(int, optional)
# :guest_id => 'otherGuest', #(string, optional)
# :disk_size => 4096, #(in MB, optional)
# :memory => 128, #(in MB, optional)
# :datastore => datastore1 #(string, optional)
+ # :disk_file => path to vmdk inside datastore (optional)
+ # :disk_type => flat, sparse (default flat)
# }
#
# Default values above.
def create_vm(specification)
spec = specification
@@ -96,11 +109,11 @@
spec[:datastore] = '[datastore1]'
end
vm_cfg = {
:name => spec[:vm_name],
:guestId => spec[:guest_id],
- :files => { :vmPathName => '[datastore1]' },
+ :files => { :vmPathName => spec[:datastore] },
:numCPUs => spec[:cpus],
:memoryMB => spec[:memory],
:deviceChange => [
{
:operation => :add,
@@ -109,23 +122,10 @@
:busNumber => 0,
:sharedBus => :noSharing)
},
{
:operation => :add,
- :fileOperation => :create,
- :device => RbVmomi::VIM.VirtualDisk(
- :key => 0,
- :backing => RbVmomi::VIM.VirtualDiskFlatVer2BackingInfo(
- :fileName => spec[:datastore],
- :diskMode => :persistent,
- :thinProvisioned => true),
- :controllerKey => 1000,
- :unitNumber => 0,
- :capacityInKB => spec[:disk_size])
- },
- {
- :operation => :add,
:device => RbVmomi::VIM.VirtualE1000(
:key => 0,
:deviceInfo => {
:label => 'Network Adapter 1',
:summary => 'VM Network'
@@ -141,10 +141,14 @@
:key => 'bios.bootOrder',
:value => 'ethernet0'
}
]
}
+ vm_cfg[:deviceChange].push(create_disk_spec(:disk_file => spec[:disk_file],
+ :disk_type => spec[:disk_type],
+ :disk_size => spec[:disk_size],
+ :datastore => spec[:datastore]))
VM.wrap(@_datacenter.vmFolder.CreateVM_Task(:config => vm_cfg, :pool => @_datacenter.hostFolder.children.first.resourcePool).wait_for_completion)
end
# Return product info as an array of strings containing
#
@@ -170,10 +174,101 @@
vms << VM.wrap(x)
end
vms
end
+ #
+ # Run a command in the ESX host via SSH
+ #
+ def remote_command(cmd)
+ Net::SSH.start(@address, @user, :password => @password) do |ssh|
+ ssh.exec! cmd
+ end
+ end
+
+ #
+ # Upload file
+ #
+ def upload_file(source, dest, print_progress = true)
+ Net::SSH.start(@address, @user, :password => @password) do |ssh|
+ puts "Uploading file... (#{File.basename(source)})"
+ 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)
+ 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)})"
+ 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..."
+ 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
+ # datastore
+ # disk_size
+ # disk_type
+ #
+ def create_disk_spec(params)
+ disk_type = params[:disk_type] || :flat
+ disk_file = params[:disk_file]
+ if disk_type == :sparse and disk_file.nil?
+ raise Exception.new("Creating sparse disks in ESX is not supported. Use an existing image.")
+ end
+ disk_size = params[:disk_size]
+ datastore = params[:datastore]
+ datastore = datastore + " #{disk_file}" if not disk_file.nil?
+ spec = {}
+ if disk_type == :sparse
+ spec = {
+ :operation => :add,
+ :device => RbVmomi::VIM.VirtualDisk(
+ :key => 0,
+ :backing => RbVmomi::VIM.VirtualDiskSparseVer2BackingInfo(
+ :fileName => datastore,
+ :diskMode => :persistent),
+ :controllerKey => 1000,
+ :unitNumber => 0,
+ :capacityInKB => disk_size)
+ }
+ else
+ spec = {
+ :operation => :add,
+ :device => RbVmomi::VIM.VirtualDisk(
+ :key => 0,
+ :backing => RbVmomi::VIM.VirtualDiskFlatVer2BackingInfo(
+ :fileName => datastore,
+ :diskMode => :persistent),
+ :controllerKey => 1000,
+ :unitNumber => 0,
+ :capacityInKB => disk_size)
+ }
+ end
+ spec[:fileOperation] = :create if disk_file.nil?
+ spec
+ end
end
class VM
attr_accessor :memory_size, :cpus, :ethernet_cards_number
@@ -229,28 +324,98 @@
# vm_object.ReconfigVM_Task(:spec => spec).wait_for_completion
#end
vm_object.Destroy_Task.wait_for_completion
end
+ def guest_info
+ GuestInfo.wrap(vm_object.guest)
+ end
+
+ #
+ # Shortcut to GuestInfo.ip_address
+ #
+ def ip_address
+ guest_info.ip_address
+ end
+
end
+ class NetworkInterface
+
+ attr_accessor :_wrapped_object
+
+ def self.wrap(obj)
+ ni = NetworkInterface.new
+ ni._wrapped_object = obj
+ ni
+ end
+
+ def ip_address
+ _wrapped_object.ipAddress.first
+ end
+
+ def mac
+ _wrapped_object.ipAddress.last
+ end
+
+ end
+
+ class GuestInfo
+
+ attr_accessor :_wrapped_object
+
+ def self.wrap(obj)
+ gi = GuestInfo.new
+ gi._wrapped_object = obj
+ gi
+ end
+
+ def ip_address
+ _wrapped_object.ipAddress
+ end
+
+ def nics
+ n = []
+ _wrapped_object.net.each do |nic|
+ n << NetworkInterface.wrap(nic)
+ end
+ n
+ end
+
+ def tools_running_status
+ _wrapped_object.toolsRunningStatus
+ end
+
+ def vmware_tools_installed?
+ _wrapped_object.toolsRunningStatus != 'guestToolsNotRunning'
+ end
+
+ end
+
class Datastore
attr_accessor :name, :capacity, :datastore_type, :free_space, :accessible
attr_accessor :url
+ # Internal use only
+ attr_accessor :_wrapped_object
#
# Internal method. Do not use
#
def self.wrap(ds)
- @_datastore = ds
_ds = Datastore.new
+ _ds._wrapped_object = ds
_ds.name = ds.summary.name
_ds.capacity = ds.summary.capacity
_ds.free_space = ds.summary.freeSpace
_ds.datastore_type = ds.summary.type
_ds.accessible = ds.summary.accessible
_ds.url = ds.summary.url
_ds
end
+
+ def method_missing(name, *args)
+ @_wrapped_object.send name, *args
+ end
+
end
end