Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow VMware provision without needing to specify a host #11437

Merged
merged 10 commits into from
Sep 27, 2016
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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})]")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down