Placement Profile – Best Fit Cluster using Tags

CloudFORMS has workflows for many different tasks including approval, quotas and placement to name just a few. This blog entry is going to add to the placement category of workflows. A previous post of mine showed how you could place new workloads NOT_NEAR “Workload Placement by Type (Not Near That)” other workloads which I still think is really cool. This placement workflow is quite simple, it matches template tags against cluster tags. Example;

  • Template Blue should only be provisioned into some Clusters.
  • Template Red should only be provisioned into other Clusters.

Now CloudFORMS out of the box ships with a workflow called best_fit_cluster that only allows the cluster that the template resides in to be used for the provisioning scope ( the hosts and storages that will be used ). This example I am gonna call best_fit_cluster_by_tag, because my example allows the template origin to be a different cluster than the destination. The solution here is quite simple;

Tag the template with a Provisioning Scope tag of your choice, then place the same tag onto Clusters that you wish to include in the scope. Example;

  • Template Blue is tagged with Prov_Scope/Applications
  • Template Red is tagged with Prov_Scope/Databases

Now tag two clusters as follows;

  • Cluster 1 is tagged with Prov_Scope/Applications
  • Cluster 2 is tagged with Prov_Scope/Databases

The workflow will ensure that when Template Blue is selected, only Cluster 1 will be processed for placement, all hosts and storages will be evaluated within the cluster, usual space requirements and host power are considered. If Template Red is selected then only Cluster 2 is used.

So how is the workflow wired in?

Create an instance and method as follows;

Instance  = EVMApplications / Provisioning / Where / best_fit_cluster_by_tags

Method = EVMApplications / Provisioning / Where / best_fit_cluster_by_tags

You can download the workflow method here..best_fit_cluster_by_tags.rb

When testing this, ensure you select “Choose Automatically” within the Environment TAB of a provisioning dialog. This causes this workflow to execute. I often create a dialog that has the Environment TAB disabled from view with the “Choose Automatically” check box set to True by default. That way you do not need to worry about if this workflow runs or not, as it always will for any user groups configured to use the dialog. Here is a sample dialog you can use as reference. developers.yaml

###################################
#
# EVM Automate Method: best_fit_with_tags
#
# Notes: This method is used to find all hosts, datastores that match the required tag
#
###################################
begin
  @method = 'best_fit_cluster_by_tags'
  $evm.log("info", "===== EVM Automate Method:  Started")

  # Turn of verbose logging
  @debug = true

  #
  # Get variables
  #
  prov = $evm.root["miq_provision"]
  vm = prov.vm_template
  raise "VM not specified" if vm.nil?
  ems  = vm.ext_management_system
  raise "EMS not found for VM [#{vm.name}" if ems.nil?
  tags = prov.get_tags

tags = vm.tags

$evm.log("info", "Template Tags - #{vm.tags}")

tags.each  do  |t|
 s = t.split("/")
  if s[0] == 'prov_scope'
      @prov_tag = s[1]
  end
end

$evm.log("info", "Template is tagged with - #{@prov_tag}")

myclusters = $evm.vmdb("ems_cluster").all
myclusters.each do | cluster |
  cluster_tags = cluster.tags
     cluster_tags.each  do  |t|
     s = t.split("/")
     if s[0] == 'prov_scope'
        @clus_tag = s[1]
        if @clus_tag == @prov_tag
          @clus_hosts = cluster.hosts
        end
     end
  end
end

$evm.log("info", "Cluster(s) is/are tagged with - #{@clus_tag}")
@clus_hosts.each do | host |
    $evm.log("info", "Host in cluster -- #{host.name}")
end

  # Log all provisioning options and space required
  $evm.log("info", "options: #{prov.options.inspect}") if @debug
  $evm.log("info", "Inline Method:  -- vm=[#{vm.name}], space required=[#{vm.provisioned_storage}]")

  # 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)

  host = storage = nil
  min_registered_vms = nil
  @clus_hosts.each { |h|
#    next unless h.power_state == "on"
    $evm.log("info", "Looking at host -- #{h.name}")
    nvms = h.vms.length

    # Filter out storages that do not have enough free space for the Vm
    storages = h.storages.find_all { |s|
      if s.free_space > vm.provisioned_storage
        $evm.log("info", "Storage Space -- #{s.free_space} is > than #{vm.provisioned_storage}")
        true
      else
        $evm.log("info", "Skipping Datastore: [#{s.name}], not enough free space for VM. Available: [#{s.free_space}], Needs: [#{vm.provisioned_storage}]")
        false
      end
    }
    # Filter out storages number of VMs is greater than the max number of VMs
    storages = storages.find_all { |s|
      if (storage_max_vms == 0) || (s.vms.size < storage_max_vms)
        true
      else
        $evm.log("info", "Skipping Datastore: [#{s.name}], max number of VMs exceeded")
        false
      end
    }
    # Filter out storages where percent used is greater than the max.
    storages = storages.find_all { |s|
      if (storage_max_pct_used == 100) || (s.v_used_space_percent_of_total < storage_max_pct_used)
        true
      else
        $evm.log("info", "Skipping Datastore: [#{s.name}], percent of used space is exceeded")
        false
      end
    }
    # if minimum registered vms is nil or number of vms on a host is greater than nil
    if min_registered_vms.nil? || nvms < min_registered_vms
      s = storages.sort { |a,b| a.free_space  b.free_space }.last
      unless s.nil?
        host    = h
        storage = s
        min_registered_vms = nvms
      end
    end
  }

  # Set host and storage
  obj = $evm.object
  obj["host"]    = host    unless host.nil?
  obj["storage"] = storage unless storage.nil?

  $evm.log("info", "Inline Method:  -- vm=[#{vm.name}] host=[#{host}] storage=[#{storage}]")

  #
  # Exit method
  #
  $evm.log("info", "===== EVM Automate Method:  Ended")
  exit MIQ_OK

  #
  # Set Ruby rescue behavior
  #
rescue => err
  $evm.log("error", ": [#{err}]n#{err.backtrace.join("n")}")
  exit MIQ_ABORT
end

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