Use Case – I want that when I provision a virtual machine I can specify certain workload types that I wish to avoid being placed with.
Example 1 – I will be requesting a virtual machine that will be very intensive on CPU or Disk I/O, therefore I want to ensure that I do not place it with any Database Servers, as I may impact their operation or they could equally stave me of resources. But I don’t know where the Database servers are located nor do I care, also real time the database servers are DRS managed therefore they may not be where they were first provisioned!
Example 2 – Regulatory reasons mean I cannot place workloads that will contain customer data near any workloads that are multi homed to more than one network.
Both examples will use the same outline process. Here is the breakdown;
All workloads will be auto tagged as their types, in example 1 this would be “Database” and in example 2 this would be “MultiHomed”
If you wish to know more about auto tagging check a future blog post š
Inside of Automate you will find a lovely method called “EVMApplications / Provisioning / Where / best_fit_with_scope” this method will during provisioning place the destination virtual machine on a host and datastore that the provisioner has the visibility/scope to use. This is important because any new method we are going to write to meet our use case should as a foundation equal this basic functionality, we do not in our new method want to break this by allowing placement to any host or cluster that the provisioner may not have rights too.
Quick lesson in provisioning profiles – These are simple profiles of tasks that will be executed during a provisioning request. You will find the profiles stored within automate in VMApplications / Provisioning / Profile / VM /. To use provisioning profiles you either login as admin, and edit the EvmGroup-super_administrator instance (not advised!) or the proper way would be to create a new user, group and connect to a role. Then create a new provisioning profile instance for your new group. Now you will be free to edit away, only affecting your new user which you can test in another browser window. I will assume now that we have created a new user and group named “evmops”. The tasks listed include how to name the virtual machine, where to place it, what approval process to use and the same for quotas too.
The new provisioning profile instance “evmops” will inherit the defaults from the schema, we are going to change one of the settings, the WHERE relationship.
Change the WHERE to – /EVMApplications/Provisioning/Where/not_near_tagged#create
This is pointing to a new instance that we now need to create, so under “/EVMApplications/Provisioning/Where”, create the instance “not_near_tagged”, and set the METH1 to “not_near_tagged”, this is a method that we now need to create.
The not_near_tagged method is going to be based on the best_fit_with_scope method to be found in the same class. here is the final code, you will still need to adjust the dialog and tag categories too, describe below.
Full code download fromĀ https://github.com/jonnyfiveiq/CloudFORMSNOW/blob/master/CFME/Provisioning/Workload%20Placement%20%7BWhere%7D/not_near_tagged.rb
###################################
#
# EVM Automate Method: best_fit_with_scope
#
# Notes: This method is used to find all hosts, datastores that have the tag category
# prov_scope = 'all' &&|| prov_scope = <group-name>
#
###################################
begin
@method = 'best_fit_with_scope'
$evm.log("info", "#{@method} - EVM Automate Method: <#{@method}> Started")
# Turn of verbose logging
@debug = true
#
# Get variables
#
prov = $evm.root["miq_provision"]
vm = prov.vm_template
raise "#{@method} - VM not specified" if vm.nil?
user = prov.miq_request.requester
raise "#{@method} - User not specified" if user.nil?
ems = vm.ext_management_system
raise "#{@method} - EMS not found for VM:<#{vm.name}>" if ems.nil?
# Log all provisioning options and space required
#$evm.log("info", "Inspecting provisioning object: #{prov.options.inspect}") if @debug
#$evm.log("info", "VM=<#{vm.name}>, Space Required=<#{vm.provisioned_storage}>")
attrs = $evm.object.attributes
tags = {}
#############################
# Get Tags that are in scope
# Default is to look for Hosts and Datastores tagged with prov_scope = All or match to Group
#############################
tags["prov_scope"] = ["all",user.normalized_ldap_group]
$evm.log("info", "#{@method} - VM=<#{vm.name}>, Space Required=<#{vm.provisioned_storage}>, group=<#{user.normalized_ldap_group}>") if @debug
#############################
# STORAGE LIMITATIONS
#############################
STORAGE_MAX_VMS = 0
storage_max_vms = $evm.object['storage_max_vms']
storage_max_vms = storage_max_vms.strip.to_i if storage_max_vms.kind_of?(String) && !storage_max_vms.strip.empty?
storage_max_vms = STORAGE_MAX_VMS unless storage_max_vms.kind_of?(Numeric)
STORAGE_MAX_PCT_USED = 100
storage_max_pct_used = $evm.object['storage_max_pct_used']
storage_max_pct_used = storage_max_pct_used.strip.to_i if storage_max_pct_used.kind_of?(String) && !storage_max_pct_used.strip.empty?
storage_max_pct_used = STORAGE_MAX_PCT_USED unless storage_max_pct_used.kind_of?(Numeric)
$evm.log("info","#{@method} - storage_max_vms:<#{storage_max_vms}> storage_max_pct_used:<#{storage_max_pct_used}>") if @debug
#############################
# Set host sort order here
# options: :active_provioning_memory, :active_provioning_cpu, :current_memory_usage,
# :current_memory_headroom, :current_cpu_usage, :random
#############################
HOST_SORT_ORDER = [:active_provioning_memory, :current_memory_headroom, :random]
#############################
# Sort hosts
#############################
active_prov_data = prov.check_quota(:active_provisions)
sort_data = []
ems.hosts.each do |h|
sort_data << sd = [[], h.name, h]
host_id = h.attributes['id'].to_i
HOST_SORT_ORDER.each do |type|
sd[0] << case type
# Multiply values by (-1) to cause larger values to sort first
when :active_provioning_memory
active_prov_data[:active][:memory_by_host_id][host_id]
when :active_provioning_cpu
active_prov_data[:active][:cpu_by_host_id][host_id]
when :current_memory_headroom
h.current_memory_headroom * -1
when :current_memory_usage
h.current_memory_usage
when :current_cpu_usage
h.current_cpu_usage
when :random
rand(1000)
else 0
end
##### End sort options
end
end
sort_data.sort! {|a,b| a[0] <=> b[0]}
hosts = sort_data.collect {|sd| sd.pop}
$evm.log("info", "#{@method} - Sorted host Order:<#{HOST_SORT_ORDER.inspect}> Results:<#{sort_data.inspect}>") if @debug
#############################
# Set storage sort order here
# options: :active_provisioning_vms, :free_space, :free_space_percentage, :random
#############################
STORAGE_SORT_ORDER = [:active_provisioning_vms, :random]
host = storage = nil
min_registered_vms = nil
hosts.each do |h|
next unless h.power_state == "on"
#############################
# Only consider hosts that have the required tags
#############################
next unless tags.all? do |key, value|
if value.kind_of?(Array)
value.any? { |v| h.tagged_with?(key, v) }
else
h.tagged_with?(key, value)
end
end
#############################
# Drop out any hosts that contain virtual machines tagged with xxxxxx
#############################
provTags = prov.get_tag(:excludeworkload)
$evm.log("info","--- PROVISIONING TAGS --- #{provTags}")
dropout = false
$evm.log("info","========== #{h.name}")
h.vms.each do | vm |
$evm.log("info","----------VM Found : #{vm.name}")
vm.tags.each do | key,value |
if key.split('/')[1] == provTags
$evm.log("info","---------Tag Found : #{key}")
$evm.log("info","---------Excluding Host : #{h.name}")
dropout = true
end
end
end
$evm.log("info", "=====DROPOUT==== #{dropout}")
next unless dropout == false
nvms = h.vms.length
#############################
# Only consider storages that have the tag category group=all
#############################
storages = h.storages.find_all do |s|
tags.all? do |key, value|
if value.kind_of?(Array)
value.any? { |v| s.tagged_with?(key, v) }
else
s.tagged_with?(key, value)
end
end
end
$evm.log("info", "#{@method} - Evaluating storages:<#{storages.collect {|s| s.name}.join(", ")}>") if @debug
#############################
# Filter out storages that do not have enough free space for the VM
#############################
active_prov_data = prov.check_quota(:active_provisions)
storages = storages.find_all { |s|
storage_id = s.attributes['id'].to_i
actively_provisioned_space = active_prov_data[:active][:storage_by_id][storage_id]
if s.free_space > vm.provisioned_storage + actively_provisioned_space
# $evm.log("info", "Active Provision Data inspect: [#{active_prov_data.inspect}]")
# $evm.log("info", "Active provision space requirement: [#{actively_provisioned_space}]")
# $evm.log("info", "Valid Datastore: [#{s.name}], enough free space for VM -- Available: [#{s.free_space}], Needs: [#{vm.provisioned_storage}]")
true
else
$evm.log("info", "#{@method} - Skipping Datastore:<#{s.name}>, not enough free space for VM:<#{vm.name}>. Available:<#{s.free_space}>, Needs:<#{vm.provisioned_storage}>") if @debug
false
end
}
#############################
# Filter out storages number of VMs is greater than the max number of VMs allowed per Datastore
#############################
storages = storages.find_all { |s|
storage_id = s.attributes['id'].to_i
active_num_vms_for_storage = active_prov_data[:active][:vms_by_storage_id][storage_id].length
if (storage_max_vms == 0) || ((s.vms.size + active_num_vms_for_storage) < storage_max_vms)
true
else
$evm.log("info", "#{@method} - Skipping Datastore:<#{s.name}>, max number of VMs:<#{s.vms.size + active_num_vms_for_storage}> exceeded") if @debug
false
end
}
#############################
# Filter out storages where percent used will be greater than the max % allowed per Datastore
#############################
storages = storages.find_all { |s|
storage_id = s.attributes['id'].to_i
active_pct_of_storage = ((active_prov_data[:active][:storage_by_id][storage_id]) / s.total_space.to_f) * 100
request_pct_of_storage = (vm.provisioned_storage / s.total_space.to_f) * 100
# $evm.log("info", "Active Provision Data inspect: [#{s.name}]:[#{storage_id}] -- [#{active_prov_data.inspect}]")
# $evm.log("info", "Datastore Percent: [#{s.name}]:[#{storage_id}] -- Storage:[#{s.v_used_space_percent_of_total}] Active:[#{active_pct_of_storage}] Request:[#{request_pct_of_storage}]")
if (storage_max_pct_used == 100) || ((s.v_used_space_percent_of_total + active_pct_of_storage + request_pct_of_storage) < storage_max_pct_used)
# $evm.log("info", "Current PCT of active provision: [#{active_pct_of_storage}]")
# $evm.log("info", "Valid Datastore: [#{s.name}], enough free space for VM -- Total Datastore Size: [#{s.total_space}], Available: [#{s.free_space}], Needs: [#{vm.provisioned_storage}]")
true
else
$evm.log("info", "#{@method} - Skipping Datastore:<#{s.name}> percent of used space #{s.v_used_space_percent_of_total + active_pct_of_storage + request_pct_of_storage} exceeded") if @debug
# $evm.log("info", "Total Datastore Size: [#{s.total_space}], Total Percentage Required: ([#{s.v_used_space_percent_of_total}] + [#{active_pct_of_storage}])")
false
end
}
if min_registered_vms.nil? || nvms < min_registered_vms
#############################
# Sort storage to determine target datastore
#############################
sort_data = []
storages.each_with_index do |s, idx|
sort_data << sd = [[], s.name, idx]
storage_id = s.attributes['id'].to_i
STORAGE_SORT_ORDER.each do |type|
sd[0] << case type
when :free_space
# Multiply values by (-1) to cause larger values to sort first
(s.free_space - active_prov_data[:active][:storage_by_id][storage_id]) * -1
when :free_space_percentage
active_pct_of_storage = ((active_prov_data[:active][:storage_by_id][storage_id]) / s.total_space.to_f) * 100
s.v_used_space_percent_of_total + active_pct_of_storage
when :active_provioning_vms
active_prov_data[:active][:vms_by_storage_id][storage_id].length
when :random
rand(1000)
else 0
end
##### End sort options
end
end
sort_data.sort! {|a,b| a[0] <=> b[0]}
$evm.log("info", "#{@method} - Sorted storage Order:<#{STORAGE_SORT_ORDER.inspect}> Results:<#{sort_data.inspect}>") if @debug
selected_storage = sort_data.first
unless selected_storage.nil?
selected_idx = selected_storage.last
storage = storages[selected_idx]
host = h
end
$evm.log("info", "Found Host:<#{h.name}> with Tags:<#{h.tags.inspect}>") if @debug
# Stop checking if we have found both host and storage
break if host && storage
end
end # END - hosts.each
obj = $evm.object
$evm.log("info", "#{@method} - Selected Host:<#{host.nil? ? "nil" : host.name}>") if @debug
obj["host"] = host unless host.nil?
$evm.log("info", "#{@method} - Selected Datastore:<#{storage.nil? ? "nil" : storage.name}>") if @debug
obj["storage"] = storage unless storage.nil?
# Set host and storage
$evm.log("info", "#{@method} - vm=<#{vm.name}> host=<#{host}> storage=<#{storage}>") if @debug
#
# Exit method
#
$evm.log("info", "#{@method} - EVM Automate Method Ended")
exit MIQ_OK
#
# Set Ruby rescue behavior
#
rescue => err
$evm.log("error", "#{@method} - [#{err}]n#{err.backtrace.join("n")}")
exit MIQ_ABORT
end
So a lot going on, here is the simple change for workload placement;
#############################
# Drop out any hosts that contain virtual machines tagged with xxxxxx
#############################
provTags = prov.get_tag(:excludeworkload)
$evm.log("info","--- PROVISIONING TAGS --- #{provTags}")
dropout = false
$evm.log("info","========== #{h.name}")
h.vms.each do | vm |
$evm.log("info","----------VM Found : #{vm.name}")
vm.tags.each do | key,value |
if key.split('/')[1] == provTags
$evm.log("info","---------Tag Found : #{key}")
$evm.log("info","---------Excluding Host : #{h.name}")
dropout = true
end
end
end
$evm.log("info", "=====DROPOUT==== #{dropout}")
next unless dropout == false
The whole code follows this process;
First identify the hosts that match the same provisioning scope as the users group, so therefore if you have 100 hosts, but the evmops group has visibility of only 50, then the first job is to only process the hosts that match the visibility scope.
For each host that is in scope, we then process the workload type, this is the new code for not_near_tagged method.
:excludeworkload
This is a tag category that should include the tags that will be used in a string match against the workload type.
Re-Cap – You have auto-tagged or manually tagged your existing virtual machine workloads, you have some called “Database” from whatever tag category. You duplicate any tags that you wish to exclude into this NEW :excludeworkload category. So that during provisioning your users can select workload types to Not Be Near.
Lastly we now need to use this functionality, by presenting the choice to the user in form of a dialog. We shall use the simple out of the box functionality “Custom Dialogs”. It is possible with CFME to duplicate a pre-existing dialog, tailor it for your needs and hook it up to a provisioning profile. So, do this, copy the existing provisioning dialog calledĀ Sample VM Provisioning Dialog.
Find the “Purpose” tab and replace with the following;
:purpose: :description: Purpose :fields: :vm_tags: :required_method: :validate_tags :description: Containing Workloads :required: false : options: :include: - excludeworkload : order: [] :single_select: [] :exclude: [] :display: :edit :required_tags: [] :data_type: :integer :display: :show :field_order:
Notice the :include: – excludeworkload, this will enable this control in this tab to filter and display only the excludeworkload tag category, producing the following result when the user executes a dialog provision.
Dont forget to hook your new dialog up to a provisioning profile for the account you are testing with, otherwise you will not see changes you make to the dialog appear on scree.
TIP – Have two browsers, I login with Firefox as admin, and perform the edits to the dialogs and automate model using this browser, then I also have Safari logged in as my user account, where I can see the changes being reflected. This saves logging in and out as admin.
So this conclude this post, actually this is really simple stuff, in summary…
We adjusted an existing workflow method with only several lines of ruby that removes from the visibility scope any hosts that contain workloads that are tagged with the same values that the user will exclude using the provisioning dialog. To enable this functionality we added a new category and tags, along with a filter edit to the provisioning dialog.
Any questions ask away, this is pretty cool feature, I really like the fact you can define what affinities you want just as much as those you don’t!
