diff --git a/app/models/manageiq/providers/vmware/infra_manager/provision/cloning.rb b/app/models/manageiq/providers/vmware/infra_manager/provision/cloning.rb index c63b9a4c628..7a3e0a1318b 100644 --- a/app/models/manageiq/providers/vmware/infra_manager/provision/cloning.rb +++ b/app/models/manageiq/providers/vmware/infra_manager/provision/cloning.rb @@ -31,6 +31,7 @@ def prepare_for_clone_task clone_options = { :name => dest_name, + :cluster => dest_cluster, :host => dest_host, :datastore => dest_datastore, :folder => dest_folder, @@ -54,18 +55,17 @@ def dest_resource_pool resource_pool = ResourcePool.find_by(:id => respool_id) unless respool_id.nil? return resource_pool unless resource_pool.nil? - cluster = dest_host.owning_cluster - cluster ? cluster.default_resource_pool : dest_host.default_resource_pool + dest_cluster.try(:default_resource_pool) || dest_host.default_resource_pool end def dest_folder folder_id = get_option(:placement_folder_name) return EmsFolder.find_by(:id => folder_id) if folder_id - host_dc = dest_host.parent_datacenter || dest_host.ems_cluster.parent_datacenter + dc = dest_cluster.try(:parent_datacenter) || dest_host.parent_datacenter # Pick the parent folder in the destination datacenter - find_folder("#{host_dc.folder_path}/vm", host_dc) + find_folder("#{dc.folder_path}/vm", dc) end def find_folder(folder_path, datacenter) @@ -78,7 +78,8 @@ def log_clone_options(clone_options) _log.info("Provisioning [#{source.name}] to [#{clone_options[:name]}]") _log.info("Source Template: [#{source.name}]") _log.info("Destination VM Name: [#{clone_options[:name]}]") - _log.info("Destination Host: [#{clone_options[:host].name} (#{clone_options[:host].ems_ref})]") + _log.info("Destination Cluster: [#{clone_options[:cluster].name} (#{clone_options[:cluster].ems_ref})]") if clone_options[:cluster] + _log.info("Destination Host: [#{clone_options[:host].name} (#{clone_options[:host].ems_ref})]") if clone_options[:host] _log.info("Destination Datastore: [#{clone_options[:datastore].name} (#{clone_options[:datastore].ems_ref})]") _log.info("Destination Folder: [#{clone_options[:folder].name}] (#{clone_options[:folder].ems_ref})") _log.info("Destination Resource Pool: [#{clone_options[:pool].name} (#{clone_options[:pool].ems_ref})]") @@ -110,7 +111,7 @@ def start_clone(clone_options) vim_clone_options[key] = ci.ems_ref_obj end - vim_clone_options[:datastore] = clone_options[:host].host_storages.find_by(:storage_id => clone_options[:datastore].id).ems_ref + vim_clone_options[:datastore] = datastore_ems_ref(clone_options) task_mor = clone_vm(vim_clone_options) _log.info("Provisioning completed for [#{vim_clone_options[:name]}] from source [#{source.name}]") if MiqProvision::CLONE_SYNCHRONOUS @@ -145,6 +146,20 @@ def clone_vm(vim_clone_options) task_mor end + def datastore_ems_ref(clone_opts) + host_ids = if clone_opts[:host] + clone_opts[:host].id + else + clone_opts[:cluster].hosts.pluck(:id) + end + + # Find a host in the cluster that has this storage mounted to get the right ems_ref for this + # datastore in the datacenter + datastore = HostStorage.find_by(:storage_id => clone_opts[:datastore].id, :host_id => host_ids) + + datastore.try(:ems_ref) + end + def get_selected_snapshot selected_snapshot = get_option(:snapshot).to_s.downcase if selected_snapshot.to_i > 0 diff --git a/app/models/manageiq/providers/vmware/infra_manager/provision/configuration/network.rb b/app/models/manageiq/providers/vmware/infra_manager/provision/configuration/network.rb index c62f89cf6e5..cebfd03e780 100644 --- a/app/models/manageiq/providers/vmware/infra_manager/provision/configuration/network.rb +++ b/app/models/manageiq/providers/vmware/infra_manager/provision/configuration/network.rb @@ -65,41 +65,38 @@ def build_config_spec_vlan(network, vnicDev, vmcs) end def build_config_spec_dvs(network, vnicDev, vmcs) - source.with_provider_connection do |vim| - operation = vnicDev.nil? ? VirtualDeviceConfigSpecOperation::Add : VirtualDeviceConfigSpecOperation::Edit - add_device_config_spec(vmcs, operation) do |vdcs| - vdcs.device = vnicDev || create_vlan_device(network) - _log.info "Setting target network device to Device Name:<#{network[:network]}> Device:<#{vdcs.device.inspect}>" - - # - # Change the port group of the target VM. - # - - vdcs.device.backing = VimHash.new('VirtualEthernetCardDistributedVirtualPortBackingInfo') do |vecdvpbi| - vecdvpbi.port = VimHash.new('DistributedVirtualSwitchPortConnection') do |dvspc| - # - # Get the DVS info for a given host. - # - dvs = vim.queryDvsConfigTarget(vim.sic.dvSwitchManager, dest_host.ems_ref_obj, nil) - dpg = vim.applyFilter(dvs.distributedVirtualPortgroup, 'uplinkPortgroup' => 'false').detect { |nupg| URI.decode(nupg.portgroupName) == network[:network] } - - raise MiqException::MiqProvisionError, "Port group [#{network[:network]}] is not available on target host [#{dest_host.name}]" if dpg.nil? - _log.info("portgroupName: #{dpg.portgroupName}, portgroupKey: #{dpg.portgroupKey}, switchUuid: #{dpg.switchUuid}") - - dvspc.switchUuid = dpg.switchUuid - dvspc.portgroupKey = dpg.portgroupKey - end - end + # A DistributedVirtualPortgroup name is unique in a datacenter so look for a Lan with this name + # on all switches in the cluster + hosts = dest_cluster.try(:hosts) || dest_host + lan = Lan.find_by(:name => network[:network], :switch_id => HostSwitch.where(:host_id => hosts).pluck(:switch_id)) + + raise MiqException::MiqProvisionError, "Port group [#{network[:network]}] is not available on target" if lan.nil? + _log.info("portgroupName: #{lan.name}, portgroupKey: #{lan.uid_ems}, switchUuid: #{lan.switch.switch_uuid}") + + operation = vnicDev.nil? ? VirtualDeviceConfigSpecOperation::Add : VirtualDeviceConfigSpecOperation::Edit + add_device_config_spec(vmcs, operation) do |vdcs| + vdcs.device = vnicDev || create_vlan_device(network) + _log.info "Setting target network device to Device Name:<#{network[:network]}> Device:<#{vdcs.device.inspect}>" + + # + # Change the port group of the target VM. + # - # - # Manually assign MAC address to target VM. - # - mac_addr = network[:mac_address] - unless mac_addr.blank? - vdcs.device.macAddress = mac_addr - vdcs.device.addressType = 'Manual' + vdcs.device.backing = VimHash.new('VirtualEthernetCardDistributedVirtualPortBackingInfo') do |vecdvpbi| + vecdvpbi.port = VimHash.new('DistributedVirtualSwitchPortConnection') do |dvspc| + dvspc.switchUuid = lan.switch.switch_uuid + dvspc.portgroupKey = lan.uid_ems end end + + # + # Manually assign MAC address to target VM. + # + mac_addr = network[:mac_address] + unless mac_addr.blank? + vdcs.device.macAddress = mac_addr + vdcs.device.addressType = 'Manual' + end end end diff --git a/app/models/manageiq/providers/vmware/infra_manager/provision/placement.rb b/app/models/manageiq/providers/vmware/infra_manager/provision/placement.rb index 1aa4b59ae4c..730ed36857d 100644 --- a/app/models/manageiq/providers/vmware/infra_manager/provision/placement.rb +++ b/app/models/manageiq/providers/vmware/infra_manager/provision/placement.rb @@ -4,36 +4,53 @@ module ManageIQ::Providers::Vmware::InfraManager::Provision::Placement protected def placement - if get_option(:placement_auto) == true + host, cluster, datastore = if get_option(:placement_auto) == true automatic_placement else manual_placement end + + raise MiqException::MiqProvisionError, "Destination placement_ds_name not provided" if datastore.nil? + raise MiqException::MiqProvisionError, "Destination placement_host_name and placement_cluster_name not provided" if host.nil? && cluster.nil? + raise MiqException::MiqProvisionError, "A Host must be selected on a non-DRS enabled cluster" if host.nil? && !cluster.drs_enabled + + return host, cluster, datastore end private def manual_placement _log.info("Manual placement...") - return selected_placement_obj(:placement_host_name, Host), - selected_placement_obj(:placement_ds_name, Storage) + + host = selected_placement_obj(:placement_host_name, Host) + cluster = selected_placement_obj(:placement_cluster_name, EmsCluster) + datastore = selected_placement_obj(:placement_ds_name, Storage) + + if host && cluster.nil? + cluster = host.ems_cluster + end + + return host, cluster, datastore end def automatic_placement # get most suitable host and datastore for new VM _log.info("Getting most suitable host and datastore for new VM from automate...") host, datastore = get_most_suitable_host_and_storage + _log.info("Host Name: [#{host.name}] Id: [#{host.id}]") if host _log.info("Datastore Name: [#{datastore.name}] ID : [#{datastore.id}]") if datastore - host ||= selected_placement_obj(:placement_host_name, Host) + + host ||= selected_placement_obj(:placement_host_name, Host) datastore ||= selected_placement_obj(:placement_ds_name, Storage) - return host, datastore + cluster = selected_placement_obj(:placement_cluster_name, EmsCluster) || host.try(:ems_cluster) + + return host, cluster, datastore end def selected_placement_obj(key, klass) klass.find_by(:id => get_option(key)).tap do |obj| - raise MiqException::MiqProvisionError, "Destination #{key} not provided" unless obj - _log.info("Using selected #{key} : [#{obj.name}] id : [#{obj.id}]") + _log.info("Using selected #{key} : [#{obj.name}] id : [#{obj.id}]") if obj end end end diff --git a/app/models/manageiq/providers/vmware/infra_manager/provision/state_machine.rb b/app/models/manageiq/providers/vmware/infra_manager/provision/state_machine.rb index 99f4244605f..2a61a7d7491 100644 --- a/app/models/manageiq/providers/vmware/infra_manager/provision/state_machine.rb +++ b/app/models/manageiq/providers/vmware/infra_manager/provision/state_machine.rb @@ -4,9 +4,10 @@ def create_destination end def determine_placement - host, datastore = placement + host, cluster, datastore = placement - options[:dest_host] = [host.id, host.name] + options[:dest_host] = [host.id, host.name] if host + options[:dest_cluster] = [cluster.id, cluster.name] if cluster options[:dest_storage] = [datastore.id, datastore.name] signal :start_clone_task end diff --git a/spec/models/manageiq/providers/vmware/infra_manager/provision/placement_spec.rb b/spec/models/manageiq/providers/vmware/infra_manager/provision/placement_spec.rb index 1eef82b91e4..2de5ace1075 100644 --- a/spec/models/manageiq/providers/vmware/infra_manager/provision/placement_spec.rb +++ b/spec/models/manageiq/providers/vmware/infra_manager/provision/placement_spec.rb @@ -5,7 +5,8 @@ template = FactoryGirl.create(:template_vmware, :ext_management_system => ems) vm = FactoryGirl.create(:vm_vmware) @storage = FactoryGirl.create(:storage) - @host = FactoryGirl.create(:host, :ext_management_system => ems, :storages => [@storage]) + @cluster = FactoryGirl.create(:ems_cluster) + @host = FactoryGirl.create(:host, :ext_management_system => ems, :storages => [@storage], :ems_cluster => @cluster) options = {:src_vm_id => template.id} @task = FactoryGirl.create(:miq_provision_vmware, :source => template, @@ -21,7 +22,13 @@ expect { @task.send(:placement) }.to raise_error(MiqException::MiqProvisionError) end - it "#manual_placement" do + it "#manual_placement without host or cluster raises error" do + @task.options[:placement_ds_name] = @storage.id + @task.options[:placement_auto] = false + expect { @task.send(:placement) }.to raise_error(MiqException::MiqProvisionError) + end + + it "#manual_placement with host and datastore" do @task.options[:placement_host_name] = @host.id @task.options[:placement_ds_name] = @storage.id @task.options[:placement_auto] = false @@ -43,8 +50,9 @@ end def check - host, storage = @task.send(:placement) + host, cluster, storage = @task.send(:placement) expect(host).to eql(@host) + expect(cluster).to eql(@cluster) expect(storage).to eql(@storage) end end diff --git a/spec/models/manageiq/providers/vmware/infra_manager/provision_spec.rb b/spec/models/manageiq/providers/vmware/infra_manager/provision_spec.rb index 99efdeb82fa..d950f78319a 100644 --- a/spec/models/manageiq/providers/vmware/infra_manager/provision_spec.rb +++ b/spec/models/manageiq/providers/vmware/infra_manager/provision_spec.rb @@ -195,7 +195,8 @@ end it "uses the resource pool from the cluster" do - @vm_prov.options[:dest_host] = [dest_host_with_cluster.id, dest_host_with_cluster.name] + @vm_prov.options[:dest_host] = [dest_host_with_cluster.id, dest_host_with_cluster.name] + @vm_prov.options[:dest_cluster] = [cluster.id, cluster.name] expect(@vm_prov.dest_resource_pool).to eq(cluster.default_resource_pool) end @@ -211,9 +212,13 @@ storage = FactoryGirl.create(:storage_nfs, :ems_ref => ds_mor, :ems_ref_obj => ds_mor) Array.new(2) do |i| + cluster_mor = "cluster-#{i}" + cluster = FactoryGirl.create(:ems_cluster, :ems_ref => cluster_mor) + host_mor = "host-#{i}" host_props = { :ext_management_system => @ems, + :ems_cluster => cluster, :ems_ref => host_mor, :ems_ref_obj => host_mor } @@ -255,6 +260,34 @@ result = @vm_prov.start_clone clone_opts expect(result).to eq(task_mor) end + + it "uses the right ems_ref when given a cluster" do + dest_cluster_mor = "cluster-1" + dest_datastore_mor = "datastore-1" + task_mor = "task-1" + + clone_opts = { + :name => @target_vm_name, + :cluster => EmsCluster.find_by(:ems_ref => dest_cluster_mor), + :datastore => Storage.first + } + + expected_vim_clone_opts = { + :name => @target_vm_name, + :wait => false, + :template => false, + :transform => nil, + :config => nil, + :customization => nil, + :linked_clone => nil, + :datastore => dest_datastore_mor + } + + allow(@vm_prov).to receive(:clone_vm).with(expected_vim_clone_opts).and_return(task_mor) + + result = @vm_prov.start_clone clone_opts + expect(result).to eq(task_mor) + end end end end