2011-09-06 19:46:42 -04:00
|
|
|
module Fog
|
|
|
|
module Compute
|
|
|
|
class Vsphere
|
|
|
|
|
2011-09-10 16:27:52 -04:00
|
|
|
module Shared
|
|
|
|
private
|
|
|
|
def vm_clone_check_options(options)
|
2012-01-19 14:38:25 -05:00
|
|
|
default_options = {
|
|
|
|
'force' => false,
|
|
|
|
'linked_clone' => false,
|
|
|
|
}
|
|
|
|
options = default_options.merge(options)
|
2011-11-10 19:01:22 -05:00
|
|
|
required_options = %w{ path name }
|
2011-09-10 16:27:52 -04:00
|
|
|
required_options.each do |param|
|
|
|
|
raise ArgumentError, "#{required_options.join(', ')} are required" unless options.has_key? param
|
2011-09-06 23:09:57 -04:00
|
|
|
end
|
2011-11-10 19:01:22 -05:00
|
|
|
# The tap removes the leading empty string
|
|
|
|
path_elements = options['path'].split('/').tap { |o| o.shift }
|
|
|
|
first_folder = path_elements.shift
|
|
|
|
if first_folder != 'Datacenters' then
|
|
|
|
raise ArgumentError, "vm_clone path option must start with /Datacenters. Got: #{options['path']}"
|
|
|
|
end
|
|
|
|
dc_name = path_elements.shift
|
|
|
|
if not self.datacenters.include? dc_name then
|
|
|
|
raise ArgumentError, "Datacenter #{dc_name} does not exist, only datacenters #{self.dacenters.join(",")} are accessible."
|
2011-09-06 23:09:57 -04:00
|
|
|
end
|
2011-09-10 16:27:52 -04:00
|
|
|
options
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Real
|
|
|
|
include Shared
|
|
|
|
def vm_clone(options = {})
|
|
|
|
# Option handling
|
|
|
|
options = vm_clone_check_options(options)
|
|
|
|
|
2011-11-10 19:01:22 -05:00
|
|
|
notfound = lambda { raise Fog::Compute::Vsphere::NotFound, "Could not find VM template" }
|
2011-09-14 13:15:19 -04:00
|
|
|
|
2011-11-10 19:01:22 -05:00
|
|
|
# Find the template in the folder. This is more efficient than
|
|
|
|
# searching ALL VM's looking for the template.
|
|
|
|
# Tap gets rid of the leading empty string and "Datacenters" element
|
|
|
|
# and returns the array.
|
|
|
|
path_elements = options['path'].split('/').tap { |ary| ary.shift 2 }
|
|
|
|
# The DC name itself.
|
|
|
|
template_dc = path_elements.shift
|
|
|
|
# If the first path element contains "vm" this denotes the vmFolder
|
|
|
|
# and needs to be shifted out
|
2012-01-10 11:56:04 -05:00
|
|
|
path_elements.shift if path_elements[0] == 'vm'
|
2011-11-10 19:01:22 -05:00
|
|
|
# The template name. The remaining elements are the folders in the
|
|
|
|
# datacenter.
|
|
|
|
template_name = path_elements.pop
|
|
|
|
# Make sure @datacenters is populated. We need the instances from the Hash keys.
|
|
|
|
self.datacenters
|
|
|
|
# Get the datacenter managed object from the hash
|
|
|
|
dc = @datacenters[template_dc]
|
|
|
|
# Get the VM Folder (Group) efficiently
|
|
|
|
vm_folder = dc.vmFolder
|
|
|
|
# Walk the tree resetting the folder pointer as we go
|
|
|
|
folder = path_elements.inject(vm_folder) do |current_folder, sub_folder_name|
|
|
|
|
# JJM VIM::Folder#find appears to be quite efficient as it uses the
|
|
|
|
# searchIndex It certainly appears to be faster than
|
|
|
|
# VIM::Folder#inventory since that returns _all_ managed objects of
|
|
|
|
# a certain type _and_ their properties.
|
|
|
|
sub_folder = current_folder.find(sub_folder_name, RbVmomi::VIM::Folder)
|
|
|
|
raise ArgumentError, "Could not descend into #{sub_folder_name}. Please check your path." unless sub_folder
|
|
|
|
sub_folder
|
2011-09-10 16:27:52 -04:00
|
|
|
end
|
|
|
|
|
2011-11-10 19:01:22 -05:00
|
|
|
# Now find the template itself using the efficient find method
|
|
|
|
vm_mob_ref = folder.find(template_name, RbVmomi::VIM::VirtualMachine)
|
|
|
|
|
2012-05-30 19:37:53 -04:00
|
|
|
# Now find _a_ resource pool to use for the clone
|
|
|
|
# (REVISIT: We need to support cloning into a specific RP)
|
|
|
|
|
|
|
|
if ( vm_mob_ref.resourcePool == nil )
|
|
|
|
# If the template is really a template then there is no associated resource pool,
|
|
|
|
# so we need to find one using the template's parent host or cluster
|
|
|
|
esx_host = vm_mob_ref.collect!('runtime.host')['runtime.host']
|
|
|
|
# The parent of the ESX host itself is a ComputeResource which has a resourcePool
|
|
|
|
resource_pool = esx_host.parent.resourcePool
|
|
|
|
else
|
|
|
|
# If the vm given did return a valid resource pool, default to using it for the clone.
|
|
|
|
# Even if specific pools aren't implemented in this environment, we will still get back
|
|
|
|
# at least the cluster or host we can pass on to the clone task
|
|
|
|
resource_pool = vm_mob_ref.resourcePool
|
|
|
|
end
|
|
|
|
|
2012-01-17 13:52:49 -05:00
|
|
|
relocation_spec=nil
|
2012-01-17 11:52:08 -05:00
|
|
|
if ( options['linked_clone'] )
|
2012-01-17 13:52:49 -05:00
|
|
|
# cribbed heavily from the rbvmomi clone_vm.rb
|
|
|
|
# this chunk of code reconfigures the disk of the clone source to be read only,
|
|
|
|
# and then creates a delta disk on top of that, this is required by the API in order to create
|
|
|
|
# linked clondes
|
2012-01-19 14:42:39 -05:00
|
|
|
disks = vm_mob_ref.config.hardware.device.select do |vm_device|
|
|
|
|
vm_device.class == RbVmomi::VIM::VirtualDisk
|
|
|
|
end
|
2012-01-17 13:52:49 -05:00
|
|
|
disks.select{|vm_device| vm_device.backing.parent == nil}.each do |disk|
|
2012-01-19 14:42:39 -05:00
|
|
|
disk_spec = {
|
|
|
|
:deviceChange => [
|
2012-01-17 13:52:49 -05:00
|
|
|
{
|
2012-01-19 14:42:39 -05:00
|
|
|
:operation => :remove,
|
|
|
|
:device => disk
|
2012-01-17 11:52:08 -05:00
|
|
|
},
|
2012-01-17 13:52:49 -05:00
|
|
|
{
|
2012-01-17 14:16:22 -05:00
|
|
|
:operation => :add,
|
|
|
|
:fileOperation => :create,
|
|
|
|
:device => disk.dup.tap{|disk_backing|
|
|
|
|
disk_backing.backing = disk_backing.backing.dup;
|
|
|
|
disk_backing.backing.fileName = "[#{disk.backing.datastore.name}]";
|
|
|
|
disk_backing.backing.parent = disk.backing
|
|
|
|
}
|
2012-01-17 13:52:49 -05:00
|
|
|
},
|
2012-01-19 14:42:39 -05:00
|
|
|
]
|
2012-01-17 11:52:08 -05:00
|
|
|
}
|
2012-01-17 13:52:49 -05:00
|
|
|
vm_mob_ref.ReconfigVM_Task(:spec => disk_spec).wait_for_completion
|
2012-01-17 11:52:08 -05:00
|
|
|
end
|
|
|
|
# Next, create a Relocation Spec instance
|
|
|
|
relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => resource_pool,
|
2012-01-19 14:42:39 -05:00
|
|
|
:diskMoveType => :moveChildMostDiskBacking)
|
|
|
|
else
|
2012-01-17 11:52:08 -05:00
|
|
|
relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => resource_pool,
|
2012-01-17 13:52:49 -05:00
|
|
|
:transform => options['transform'] || 'sparse')
|
2012-01-17 11:52:08 -05:00
|
|
|
end
|
2011-09-06 19:46:42 -04:00
|
|
|
# And the clone specification
|
|
|
|
clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => relocation_spec,
|
2011-09-10 16:27:52 -04:00
|
|
|
:powerOn => options['power_on'] || true,
|
2011-09-06 19:46:42 -04:00
|
|
|
:template => false)
|
2011-09-10 16:27:52 -04:00
|
|
|
task = vm_mob_ref.CloneVM_Task(:folder => vm_mob_ref.parent, :name => options['name'], :spec => clone_spec)
|
2011-11-10 19:01:22 -05:00
|
|
|
# Waiting for the VM to complete allows us to get the VirtulMachine
|
|
|
|
# object of the new machine when it's done. It is HIGHLY recommended
|
|
|
|
# to set 'wait' => true if your app wants to wait. Otherwise, you're
|
|
|
|
# going to have to reload the server model over and over which
|
|
|
|
# generates a lot of time consuming API calls to vmware.
|
|
|
|
if options['wait'] then
|
|
|
|
# REVISIT: It would be awesome to call a block passed to this
|
|
|
|
# request to notify the application how far along in the process we
|
|
|
|
# are. I'm thinking of updating a progress bar, etc...
|
|
|
|
new_vm = task.wait_for_completion
|
|
|
|
else
|
|
|
|
tries = 0
|
|
|
|
new_vm = begin
|
|
|
|
# Try and find the new VM (folder.find is quite efficient)
|
|
|
|
folder.find(options['name'], RbVmomi::VIM::VirtualMachine) or raise Fog::Vsphere::Errors::NotFound
|
|
|
|
rescue Fog::Vsphere::Errors::NotFound
|
|
|
|
tries += 1
|
|
|
|
if tries <= 10 then
|
|
|
|
sleep 15
|
|
|
|
retry
|
|
|
|
end
|
|
|
|
nil
|
2011-09-06 23:09:57 -04:00
|
|
|
end
|
|
|
|
end
|
2011-11-10 19:01:22 -05:00
|
|
|
|
2011-09-10 16:27:52 -04:00
|
|
|
# Return hash
|
2011-09-06 23:09:57 -04:00
|
|
|
{
|
2011-11-10 19:01:22 -05:00
|
|
|
'vm_ref' => new_vm ? new_vm._ref : nil,
|
|
|
|
'vm_attributes' => new_vm ? convert_vm_mob_ref_to_attr_hash(new_vm) : {},
|
|
|
|
'task_ref' => task._ref
|
2011-09-06 23:09:57 -04:00
|
|
|
}
|
2011-09-06 19:46:42 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
class Mock
|
2011-09-10 16:27:52 -04:00
|
|
|
include Shared
|
|
|
|
def vm_clone(options = {})
|
|
|
|
# Option handling
|
|
|
|
options = vm_clone_check_options(options)
|
2011-09-14 13:15:19 -04:00
|
|
|
notfound = lambda { raise Fog::Compute::Vsphere::NotFound, "Cloud not find VM template" }
|
|
|
|
vm_mob_ref = list_virtual_machines['virtual_machines'].find(notfound) do |vm|
|
2011-11-10 19:01:22 -05:00
|
|
|
vm['name'] == options['path'].split("/")[-1]
|
2011-09-14 13:15:19 -04:00
|
|
|
end
|
2011-09-06 23:09:57 -04:00
|
|
|
{
|
2011-09-10 16:27:52 -04:00
|
|
|
'vm_ref' => 'vm-123',
|
2012-01-19 14:42:39 -05:00
|
|
|
'task_ref' => 'task-1234',
|
2011-09-06 23:09:57 -04:00
|
|
|
}
|
2011-09-06 19:46:42 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|