From 4c858d012a262093383e57ea6db790521886d8d4 Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Tue, 24 Sep 2013 11:27:46 -0700 Subject: [PATCH] Initial --- lib/vsphere_helper.rb | 155 ++++++++++++++++++++++++++++++++++++++++++ vmware-host-pooler | 129 +++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100755 lib/vsphere_helper.rb create mode 100755 vmware-host-pooler diff --git a/lib/vsphere_helper.rb b/lib/vsphere_helper.rb new file mode 100755 index 0000000..534dee0 --- /dev/null +++ b/lib/vsphere_helper.rb @@ -0,0 +1,155 @@ +require 'yaml' unless defined?(YAML) +require 'rubygems' unless defined?(Gem) + +class VsphereHelper + def initialize vInfo = {} + begin + require 'rbvmomi' + rescue LoadError + raise "Unable to load RbVmomi, please ensure its installed" + end + + fog_file = File.expand_path("~/.fog") + fog_data = YAML.load_file(fog_file)[:default] + + @connection = RbVmomi::VIM.connect :host => fog_data[:vsphere_server], + :user => fog_data[:vsphere_username], + :password => fog_data[:vsphere_password], + :insecure => true + end + + def find_snapshot vm, snapname + search_child_snaps vm.snapshot.rootSnapshotList, snapname + end + + def search_child_snaps tree, snapname + snapshot = nil + tree.each do |child| + if child.name == snapname + snapshot ||= child.snapshot + else + snapshot ||= search_child_snaps child.childSnapshotList, snapname + end + end + snapshot + end + + def find_customization name + csm = @connection.serviceContent.customizationSpecManager + + begin + customizationSpec = csm.GetCustomizationSpec({:name => name}).spec + rescue + customizationSpec = nil + end + + return customizationSpec + end + + # an easier wrapper around the horrid PropertyCollector interface, + # necessary for searching VMs in all Datacenters that may be nested + # within folders of arbitrary depth + # returns a hash array of => + def find_vms names, connection = @connection + names = names.is_a?(Array) ? names : [ names ] + containerView = get_base_vm_container_from connection + propertyCollector = connection.propertyCollector + + objectSet = [{ + :obj => containerView, + :skip => true, + :selectSet => [ RbVmomi::VIM::TraversalSpec.new({ + :name => 'gettingTheVMs', + :path => 'view', + :skip => false, + :type => 'ContainerView' + }) ] + }] + + propSet = [{ + :pathSet => [ 'name' ], + :type => 'VirtualMachine' + }] + + results = propertyCollector.RetrievePropertiesEx({ + :specSet => [{ + :objectSet => objectSet, + :propSet => propSet + }], + :options => { :maxObjects => nil } + }) + + vms = {} + results.objects.each do |result| + name = result.propSet.first.val + next unless names.include? name + vms[name] = result.obj + end + + while results.token do + results = propertyCollector.ContinueRetrievePropertiesEx({:token => results.token}) + results.objects.each do |result| + name = result.propSet.first.val + next unless names.include? name + vms[name] = result.obj + end + end + vms + end + + def find_datastore datastorename + datacenter = @connection.serviceInstance.find_datacenter + datacenter.find_datastore(datastorename) + end + + def find_folder foldername + datacenter = @connection.serviceInstance.find_datacenter + base = datacenter.vmFolder + folders = foldername.split('/') + folders.each do |folder| + case base + when RbVmomi::VIM::Folder + base = base.childEntity.find { |f| f.name == folder } + else + abort "Unexpected object type encountered (#{base.class}) while finding folder" + end + end + + base + end + + def find_pool poolname + datacenter = @connection.serviceInstance.find_datacenter + base = datacenter.hostFolder + pools = poolname.split('/') + pools.each do |pool| + case base + when RbVmomi::VIM::Folder + base = base.childEntity.find { |f| f.name == pool } + when RbVmomi::VIM::ClusterComputeResource + base = base.resourcePool.resourcePool.find { |f| f.name == pool } + when RbVmomi::VIM::ResourcePool + base = base.resourcePool.find { |f| f.name == pool } + else + abort "Unexpected object type encountered (#{base.class}) while finding resource pool" + end + end + + base = base.resourcePool unless base.is_a?(RbVmomi::VIM::ResourcePool) and base.respond_to?(:resourcePool) + base + end + + def get_base_vm_container_from connection + viewManager = connection.serviceContent.viewManager + viewManager.CreateContainerView({ + :container => connection.serviceContent.rootFolder, + :recursive => true, + :type => [ 'VirtualMachine' ] + }) + end + + def close + @connection.close + end +end + diff --git a/vmware-host-pooler b/vmware-host-pooler new file mode 100755 index 0000000..1281d46 --- /dev/null +++ b/vmware-host-pooler @@ -0,0 +1,129 @@ +#!/usr/bin/ruby + +require 'rbvmomi' +require 'redis' +require 'yaml' + +require File.expand_path( File.dirname( __FILE__ ), './lib/vsphere_helper.rb' ) +vsphere_helper = VsphereHelper.new + +# Load the pool configuration +pools = YAML.load_file('vmware-host-pooler.yaml')[:pools] + +# Load fog credentials +fog_file = File.expand_path("~/.fog") +fog_data = YAML.load_file(fog_file)[:default] + +# Connect to vSphere +$vim = RbVmomi::VIM.connect( + :host => fog_data[:vsphere_server], + :user => fog_data[:vsphere_username], + :password => fog_data[:vsphere_password], + :ssl => true, + :insecure => true, + :rev => '5.1' +) + +# Connect to Redis +$redis = Redis.new + + + +loop do + pools.each do |pool| + total = 0 + + datacenter = $vim.serviceInstance.find_datacenter + base = datacenter.hostFolder + path = pool['pool'].split('/') + + # Locate the resource pool + path.each do |p| + case base + when RbVmomi::VIM::Folder + base = base.childEntity.find { |f| f.name == p } + when RbVmomi::VIM::ClusterComputeResource + base = base.resourcePool.resourcePool.find { |f| f.name == p } + when RbVmomi::VIM::ResourcePool + base = base.resourcePool.find { |f| f.name == p } + else + abort "Unexpected object type encountered (#{base.class}) while finding resource pool" + end + end + base = base.resourcePool unless base.is_a?(RbVmomi::VIM::ResourcePool) and base.respond_to?(:resourcePool) + + # Count the number of VMs and correlate Redis inventory + inventory = {} + base.vm.each do |vm| + if ! $redis.sismember('vmware_host_pool-'+pool['name'], vm['name']) + $redis.sadd('vmware_host_pool-'+pool['name'], vm['name']) + end + inventory[vm['name']] = 1 + total = total + 1 + end + + $redis.smembers('vmware_host_pool-'+pool['name']).each do |vm| + if ! inventory[vm] + $redis.srem('vmware_host_pool-'+pool['name'], vm) + end + end + + # Bring the pool up to the desired size + if total < pool['size'] + + # Provision VMs + (1..(pool['size']-total)).each { |i| + vm = {} + + if pool['template'] =~ /\// + templatefolders = pool['template'].split('/') + vm['template'] = templatefolders.pop + end + + if templatefolders + vm[vm['template']] = vsphere_helper.find_folder(templatefolders.join('/')).find(vm['template']) + else + raise "Please provide a full path to the template" + end + + if vm['template'].length == 0 + raise "Unable to find template '#{h['template']}'!" + end + + # Generate a randomized hostname + o = [('a'..'z'),('0'..'9')].map{|r| r.to_a}.flatten + vm['hostname'] = o[rand(25)]+(0...14).map{o[rand(o.length)]}.join + + # Put the VM in the specified folder and resource pool + relocateSpec = RbVmomi::VIM.VirtualMachineRelocateSpec( + :datastore => vsphere_helper.find_datastore(pool['datastore']), + :pool => vsphere_helper.find_pool(pool['pool']), + :diskMoveType => :moveChildMostDiskBacking + ) + + # Create a clone spec + spec = RbVmomi::VIM.VirtualMachineCloneSpec( + :location => relocateSpec, + :powerOn => true, + :template => false + ) + + puts '[+] '+vm.inspect + + # Clone the VM + vm[vm['template']].CloneVM_Task( + :folder => vsphere_helper.find_folder(pool['folder']), + :name => vm['hostname'], + :spec => spec + ).wait_for_completion + + # Add VM to Redis inventory + $redis.sadd('vmware_host_pool-'+pool['name'], vm['hostname']) + } + end + + # ZZZzzz... + sleep(5) + end +end +