diff --git a/lib/fog/vsphere/requests/compute/vm_clone.rb b/lib/fog/vsphere/requests/compute/vm_clone.rb index 8ea9f6577..bd75147e5 100644 --- a/lib/fog/vsphere/requests/compute/vm_clone.rb +++ b/lib/fog/vsphere/requests/compute/vm_clone.rb @@ -5,7 +5,11 @@ module Fog module Shared private def vm_clone_check_options(options) - options = { 'force' => false }.merge(options) + default_options = { + 'force' => false, + 'linked_clone' => false, + } + options = default_options.merge(options) required_options = %w{ path name } required_options.each do |param| raise ArgumentError, "#{required_options.join(', ')} are required" unless options.has_key? param @@ -70,16 +74,47 @@ module Fog 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 - - # Next, create a Relocation Spec instance - relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => resource_pool, - :transform => options['transform'] || 'sparse') + relocation_spec=nil + if ( options['linked_clone'] ) + # 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 + disks = vm_mob_ref.config.hardware.device.select do |vm_device| + vm_device.class == RbVmomi::VIM::VirtualDisk + end + disks.select{|vm_device| vm_device.backing.parent == nil}.each do |disk| + disk_spec = { + :deviceChange => [ + { + :operation => :remove, + :device => disk + }, + { + :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 + } + }, + ] + } + vm_mob_ref.ReconfigVM_Task(:spec => disk_spec).wait_for_completion + end + # Next, create a Relocation Spec instance + relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => resource_pool, + :diskMoveType => :moveChildMostDiskBacking) + else + relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => resource_pool, + :transform => options['transform'] || 'sparse') + end # And the clone specification clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => relocation_spec, :powerOn => options['power_on'] || true, :template => false) task = vm_mob_ref.CloneVM_Task(:folder => vm_mob_ref.parent, :name => options['name'], :spec => clone_spec) - # 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 @@ -126,7 +161,7 @@ module Fog end { 'vm_ref' => 'vm-123', - 'task_ref' => 'task-1234' + 'task_ref' => 'task-1234', } end diff --git a/tests/vsphere/requests/compute/vm_clone_tests.rb b/tests/vsphere/requests/compute/vm_clone_tests.rb index 28cc73d0e..14b3fcb94 100644 --- a/tests/vsphere/requests/compute/vm_clone_tests.rb +++ b/tests/vsphere/requests/compute/vm_clone_tests.rb @@ -1,15 +1,27 @@ Shindo.tests("Fog::Compute[:vsphere] | vm_clone request", 'vsphere') do - #require 'guid' - template = "/Datacenters/Solutions/vm/Jeff/Templates/centos56gm2" + # require 'guid' compute = Fog::Compute[:vsphere] + response = nil + response_linked = nil - tests("The return value should") do - response = compute.vm_clone('path' => template, 'name' => 'cloning_vm') + template = "/Datacenters/Solutions/vm/Jeff/Templates/centos56gm2" + tests("Standard Clone | The return value should") do + response = compute.vm_clone('path' => template, 'name' => 'cloning_vm', 'wait' => 1) test("be a kind of Hash") { response.kind_of? Hash } %w{ vm_ref task_ref }.each do |key| test("have a #{key} key") { response.has_key? key } end end + + template = "/Datacenters/Solutions/vm/Jeff/Templates/centos56gm2" + tests("Linked Clone | The return value should") do + response = compute.vm_clone('path' => template, 'name' => 'cloning_vm_linked', 'wait' => 1, 'linked_clone' => true) + test("be a kind of Hash") { response.kind_of? Hash } + %w{ vm_ref task_ref }.each do |key| + test("have a #{key} key") { response.has_key? key } + end + end + tests("When invalid input is presented") do raises(ArgumentError, 'it should raise ArgumentError') { compute.vm_clone(:foo => 1) } raises(Fog::Compute::Vsphere::NotFound, 'it should raise Fog::Compute::Vsphere::NotFound when the UUID is not a string') do