Workload Placement by Type (Not Near That)

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}")

&nbsp;

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.

Screen Shot 2013-05-13 at 15.11.55

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!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s