class Vmpooler::PoolManager::Provider::VSphere
Constants
- ADAPTER_TYPE
vSphere helper methods
- DISK_MODE
- DISK_TYPE
Attributes
The connection_pool
method is normally used only for testing
Public Class Methods
Vmpooler::PoolManager::Provider::Base::new
# File lib/vmpooler/providers/vsphere.rb, line 14 def initialize(config, logger, metrics, redis_connection_pool, name, options) super(config, logger, metrics, redis_connection_pool, name, options) task_limit = global_config[:config].nil? || global_config[:config]['task_limit'].nil? ? 10 : global_config[:config]['task_limit'].to_i # The default connection pool size is: # Whatever is biggest from: # - How many pools this provider services # - Maximum number of cloning tasks allowed # - Need at least 2 connections so that a pool can have inventory functions performed while cloning etc. default_connpool_size = [provided_pools.count, task_limit, 2].max connpool_size = provider_config['connection_pool_size'].nil? ? default_connpool_size : provider_config['connection_pool_size'].to_i # The default connection pool timeout should be quite large - 60 seconds connpool_timeout = provider_config['connection_pool_timeout'].nil? ? 60 : provider_config['connection_pool_timeout'].to_i logger.log('d', "[#{name}] ConnPool - Creating a connection pool of size #{connpool_size} with timeout #{connpool_timeout}") @connection_pool = Vmpooler::PoolManager::GenericConnectionPool.new( metrics: metrics, connpool_type: 'provider_connection_pool', connpool_provider: name, size: connpool_size, timeout: connpool_timeout ) do logger.log('d', "[#{name}] Connection Pool - Creating a connection object") # Need to wrap the vSphere connection object in another object. The generic connection pooler will preserve # the object reference for the connection, which means it cannot "reconnect" by creating an entirely new connection # object. Instead by wrapping it in a Hash, the Hash object reference itself never changes but the content of the # Hash can change, and is preserved across invocations. new_conn = connect_to_vsphere { connection: new_conn } end @provider_hosts = {} @provider_hosts_lock = Mutex.new @redis = redis_connection_pool end
Public Instance Methods
# File lib/vmpooler/providers/vsphere.rb, line 647 def add_disk(vm, size, datastore, connection, datacentername) return false unless size.to_i > 0 vmdk_datastore = find_datastore(datastore, connection, datacentername) raise("Datastore '#{datastore}' does not exist in datacenter '#{datacentername}'") if vmdk_datastore.nil? datacenter = connection.serviceInstance.find_datacenter(datacentername) controller = find_disk_controller(vm) disk_unit_number = find_disk_unit_number(vm, controller) disk_count = vm.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk).count vmdk_file_name = "#{vm['name']}/#{vm['name']}_#{disk_count}.vmdk" vmdk_spec = RbVmomi::VIM::FileBackedVirtualDiskSpec( capacityKb: size.to_i * 1024 * 1024, adapterType: ADAPTER_TYPE, diskType: DISK_TYPE ) vmdk_backing = RbVmomi::VIM::VirtualDiskFlatVer2BackingInfo( datastore: vmdk_datastore, diskMode: DISK_MODE, fileName: "[#{datastore}] #{vmdk_file_name}" ) device = RbVmomi::VIM::VirtualDisk( backing: vmdk_backing, capacityInKB: size.to_i * 1024 * 1024, controllerKey: controller.key, key: -1, unitNumber: disk_unit_number ) device_config_spec = RbVmomi::VIM::VirtualDeviceConfigSpec( device: device, operation: RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add') ) vm_config_spec = RbVmomi::VIM::VirtualMachineConfigSpec( deviceChange: [device_config_spec] ) connection.serviceContent.virtualDiskManager.CreateVirtualDisk_Task( datacenter: datacenter, name: "[#{datastore}] #{vmdk_file_name}", spec: vmdk_spec ).wait_for_completion vm.ReconfigVM_Task(spec: vm_config_spec).wait_for_completion true end
# File lib/vmpooler/providers/vsphere.rb, line 859 def build_compatible_hosts_lists(hosts, percentage) hosts_with_arch_versions = hosts.map do |h| { 'utilization' => h[0], 'host_object' => h[1], 'architecture' => get_host_cpu_arch_version(h[1]) } end versions = hosts_with_arch_versions.map { |host| host['architecture'] }.uniq architectures = {} versions.each do |version| architectures[version] = [] end hosts_with_arch_versions.each do |h| architectures[h['architecture']] << [h['utilization'], h['host_object'], h['architecture']] end versions.each do |version| targets = select_least_used_hosts(architectures[version], percentage) architectures[version] = targets end architectures end
# File lib/vmpooler/providers/vsphere.rb, line 973 def build_propSpecs(datacenter, folder, vmname) # rubocop:disable Naming/MethodName { entity => self, :inventoryPath => "#{datacenter}/vm/#{folder}/#{vmname}" } end
# File lib/vmpooler/providers/vsphere.rb, line 588 def connect_to_vsphere max_tries = global_config[:config]['max_tries'] || 3 retry_factor = global_config[:config]['retry_factor'] || 10 try = 1 begin connection = RbVmomi::VIM.connect host: provider_config['server'], user: provider_config['username'], password: provider_config['password'], insecure: provider_config['insecure'] || false metrics.increment('connect.open') connection rescue StandardError => e metrics.increment('connect.fail') raise e if try >= max_tries sleep(try * retry_factor) try += 1 retry end end
# File lib/vmpooler/providers/vsphere.rb, line 838 def cpu_utilization_for(host) cpu_usage = host.summary.quickStats.overallCpuUsage return nil if cpu_usage.nil? cpu_size = host.summary.hardware.cpuMhz * host.summary.hardware.numCpuCores cpu_usage.fdiv(cpu_size) * 100 end
# File lib/vmpooler/providers/vsphere.rb, line 400 def create_clone_spec(relocate_spec, config_spec) RbVmomi::VIM.VirtualMachineCloneSpec( location: relocate_spec, config: config_spec, powerOn: true, template: false ) end
# File lib/vmpooler/providers/vsphere.rb, line 366 def create_config_spec(vm_name, template_name, extra_config) RbVmomi::VIM.VirtualMachineConfigSpec( annotation: JSON.pretty_generate( name: vm_name, created_by: provider_config['username'], base_template: template_name, creation_timestamp: Time.now.utc ), extraConfig: extra_config ) end
# File lib/vmpooler/providers/vsphere.rb, line 453 def create_disk(pool_name, vm_name, disk_size) pool = pool_config(pool_name) raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil? datastore_name = pool['datastore'] raise("Pool #{pool_name} does not have a datastore defined for the provider #{name}") if datastore_name.nil? @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) vm_object = find_vm(pool_name, vm_name, connection) raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if vm_object.nil? add_disk(vm_object, disk_size, datastore_name, connection, get_target_datacenter_from_config(pool_name)) end true end
# File lib/vmpooler/providers/vsphere.rb, line 1118 def create_folder(connection, new_folder, datacenter) dc = connection.serviceInstance.find_datacenter(datacenter) folder_object = dc.vmFolder.traverse(new_folder, RbVmomi::VIM::Folder, true) raise("Cannot create folder #{new_folder}") if folder_object.nil? folder_object end
# File lib/vmpooler/providers/vsphere.rb, line 378 def create_relocate_spec(target_datastore, target_datacenter_name, pool_name, connection) pool = pool_config(pool_name) target_cluster_name = get_target_cluster_from_config(pool_name) relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec( datastore: find_datastore(target_datastore, connection, target_datacenter_name), diskMoveType: get_disk_backing(pool) ) manage_host_selection = @config[:config]['manage_host_selection'] if @config[:config].key?('manage_host_selection') if manage_host_selection run_select_hosts(pool_name, @provider_hosts) target_host = select_next_host(pool_name, @provider_hosts) host_object = find_host_by_dnsname(connection, target_host) relocate_spec.host = host_object else # Choose a cluster/host to place the new VM on target_cluster_object = find_cluster(target_cluster_name, connection, target_datacenter_name) relocate_spec.pool = target_cluster_object.resourcePool end relocate_spec end
# File lib/vmpooler/providers/vsphere.rb, line 470 def create_snapshot(pool_name, vm_name, new_snapshot_name) @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) vm_object = find_vm(pool_name, vm_name, connection) raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if vm_object.nil? old_snap = find_snapshot(vm_object, new_snapshot_name) raise("Snapshot #{new_snapshot_name} for VM #{vm_name} in pool #{pool_name} already exists for the provider #{name}") unless old_snap.nil? vm_object.CreateSnapshot_Task( name: new_snapshot_name, description: 'vmpooler', memory: true, quiesce: true ).wait_for_completion end true end
# File lib/vmpooler/providers/vsphere.rb, line 1141 def create_template_delta_disks(pool) @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) template_vm_object = find_template_vm(pool, connection) template_vm_object.add_delta_disk_layer_on_all_disks end end
# File lib/vmpooler/providers/vsphere.rb, line 289 def create_vm(pool_name, new_vmname) pool = pool_config(pool_name) raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil? vm_hash = nil @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) # Assume all pool config is valid i.e. not missing template_path = pool['template'] target_folder_path = pool['folder'] target_datastore = pool['datastore'] target_datacenter_name = get_target_datacenter_from_config(pool_name) # Get the template VM object raise("Pool #{pool_name} did not specify a full path for the template for the provider #{name}") unless valid_template_path? template_path template_vm_object = find_template_vm(pool, connection) extra_config = [ { key: 'guestinfo.hostname', value: new_vmname } ] if pool.key?('snapshot_mainMem_ioBlockPages') ioblockpages = pool['snapshot_mainMem_ioBlockPages'] extra_config.push( { key: 'mainMem.ioBlockPages', value: ioblockpages } ) end if pool.key?('snapshot_mainMem_iowait') iowait = pool['snapshot_mainMem_iowait'] extra_config.push( { key: 'mainMem.iowait', value: iowait } ) end # Annotate with creation time, origin template, etc. # Add extraconfig options that can be queried by vmtools config_spec = create_config_spec(new_vmname, template_path, extra_config) # Check if alternate network configuration is specified and add configuration if pool.key?('network') template_vm_network_device = template_vm_object.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first network_name = pool['network'] network_device = set_network_device(target_datacenter_name, template_vm_network_device, network_name, connection) config_spec.deviceChange = [{ operation: 'edit', device: network_device }] end # Put the VM in the specified folder and resource pool relocate_spec = create_relocate_spec(target_datastore, target_datacenter_name, pool_name, connection) # Create a clone spec clone_spec = create_clone_spec(relocate_spec, config_spec) begin vm_target_folder = find_vm_folder(pool_name, connection) vm_target_folder ||= create_folder(connection, target_folder_path, target_datacenter_name) if @config[:config].key?('create_folders') && (@config[:config]['create_folders'] == true) rescue StandardError if @config[:config].key?('create_folders') && (@config[:config]['create_folders'] == true) vm_target_folder = create_folder(connection, target_folder_path, target_datacenter_name) else raise end end raise ArgumentError, "Cannot find the configured folder for #{pool_name} #{target_folder_path}" unless vm_target_folder # Create the new VM new_vm_object = template_vm_object.CloneVM_Task( folder: vm_target_folder, name: new_vmname, spec: clone_spec ).wait_for_completion vm_hash = generate_vm_hash(new_vm_object, pool_name) end vm_hash end
# File lib/vmpooler/providers/vsphere.rb, line 111 def destroy_folder(folder_object) try = 0 if try.nil? max_tries = 3 logger.log('s', "[-] [#{folder_object.name}] removing unconfigured folder") folder_object.Destroy_Task.wait_for_completion rescue StandardError try += 1 try >= max_tries ? raise : retry end
# File lib/vmpooler/providers/vsphere.rb, line 95 def destroy_folder_and_children(folder_object) vms = {} data_ttl = $config[:redis]['data_ttl'].to_i folder_name = folder_object.name unless folder_object.childEntity.count == 0 folder_object.childEntity.each do |vm| vms[vm.name] = vm end vms.each do |vm_name, vm_object| destroy_vm_and_log(vm_name, vm_object, folder_name, data_ttl) end end destroy_folder(folder_object) end
# File lib/vmpooler/providers/vsphere.rb, line 503 def destroy_vm(pool_name, vm_name) @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) vm_object = find_vm(pool_name, vm_name, connection) # If a VM doesn't exist then it is effectively deleted return true if vm_object.nil? # Poweroff the VM if it's running vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime&.powerState && vm_object.runtime.powerState == 'poweredOn' # Kill it with fire vm_object.Destroy_Task.wait_for_completion end true end
# File lib/vmpooler/providers/vsphere.rb, line 61 def destroy_vm_and_log(vm_name, vm_object, pool, data_ttl) try = 0 if try.nil? max_tries = 3 @redis.with_metrics do |redis| redis.multi redis.srem("vmpooler__completed__#{pool}", vm_name) redis.hdel("vmpooler__active__#{pool}", vm_name) redis.hset("vmpooler__vm__#{vm_name}", 'destroy', Time.now) # Auto-expire metadata key redis.expire("vmpooler__vm__#{vm_name}", (data_ttl * 60 * 60)) redis.exec end start = Time.now if vm_object.is_a? RbVmomi::VIM::Folder logger.log('s', "[!] [#{pool}] '#{vm_name}' is a folder, bailing on destroying") raise('Expected VM, but received a folder object') end vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime&.powerState && vm_object.runtime.powerState == 'poweredOn' vm_object.Destroy_Task.wait_for_completion finish = format('%<time>.2f', time: Time.now - start) logger.log('s', "[-] [#{pool}] '#{vm_name}' destroyed in #{finish} seconds") metrics.timing("destroy.#{pool}", finish) rescue RuntimeError raise rescue StandardError => e try += 1 logger.log('s', "[!] [#{pool}] failed to destroy '#{vm_name}' with an error: #{e}") try >= max_tries ? raise : retry end
# File lib/vmpooler/providers/vsphere.rb, line 576 def ensured_vsphere_connection(connection_pool_object) connection_pool_object[:connection] = connect_to_vsphere unless vsphere_connection_ok?(connection_pool_object[:connection]) connection_pool_object[:connection] end
# File lib/vmpooler/providers/vsphere.rb, line 930 def find_cluster(cluster, connection, datacentername) datacenter = connection.serviceInstance.find_datacenter(datacentername) raise("Datacenter #{datacentername} does not exist") if datacenter.nil? # In the event the cluster is not a direct descendent of the # datacenter, we use a ContainerView to leverage its recursive # search. This will find clusters which are, for example, in # folders under the datacenter. This will also find standalone # hosts which are not part of a cluster. cv = connection.serviceContent.viewManager.CreateContainerView( container: datacenter.hostFolder, type: ['ComputeResource', 'ClusterComputeResource'], recursive: true ) cluster = cv.view.find { |cluster_object| cluster_object.name == cluster } cv.DestroyView cluster end
# File lib/vmpooler/providers/vsphere.rb, line 699 def find_datastore(datastorename, connection, datacentername) datacenter = connection.serviceInstance.find_datacenter(datacentername) raise("Datacenter #{datacentername} does not exist") if datacenter.nil? datacenter.find_datastore(datastorename) end
# File lib/vmpooler/providers/vsphere.rb, line 706 def find_device(vm, device_name) vm.config.hardware.device.each do |device| return device if device.deviceInfo.label == device_name end nil end
# File lib/vmpooler/providers/vsphere.rb, line 714 def find_disk_controller(vm) devices = find_disk_devices(vm) devices.keys.sort.each do |device| return find_device(vm, devices[device]['device'].deviceInfo.label) if devices[device]['children'].length < 15 end nil end
# File lib/vmpooler/providers/vsphere.rb, line 724 def find_disk_devices(vm) devices = {} vm.config.hardware.device.each do |device| if device.is_a? RbVmomi::VIM::VirtualSCSIController if devices[device.controllerKey].nil? devices[device.key] = {} devices[device.key]['children'] = [] end devices[device.key]['device'] = device end if device.is_a? RbVmomi::VIM::VirtualDisk if devices[device.controllerKey].nil? devices[device.controllerKey] = {} devices[device.controllerKey]['children'] = [] end devices[device.controllerKey]['children'].push(device) end end devices end
# File lib/vmpooler/providers/vsphere.rb, line 750 def find_disk_unit_number(vm, controller) used_unit_numbers = [] available_unit_numbers = [] devices = find_disk_devices(vm) devices.keys.sort.each do |c| next unless controller.key == devices[c]['device'].key used_unit_numbers.push(devices[c]['device'].scsiCtlrUnitNumber) devices[c]['children'].each do |disk| used_unit_numbers.push(disk.unitNumber) end end (0..15).each do |scsi_id| available_unit_numbers.push(scsi_id) if used_unit_numbers.grep(scsi_id).length <= 0 end available_unit_numbers.min end
# File lib/vmpooler/providers/vsphere.rb, line 915 def find_host_by_dnsname(connection, dnsname) host_object = connection.searchIndex.FindByDnsName(dnsName: dnsname, vmSearch: false) return nil if host_object.nil? host_object end
# File lib/vmpooler/providers/vsphere.rb, line 922 def find_least_used_host(cluster, connection, datacentername) cluster_object = find_cluster(cluster, connection, datacentername) target_hosts = get_cluster_host_utilization(cluster_object) raise("There is no host candidate in vcenter that meets all the required conditions, check that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory'") if target_hosts.empty? target_hosts.min[1] end
# File lib/vmpooler/providers/vsphere.rb, line 897 def find_least_used_hosts(cluster, datacentername, percentage) @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) cluster_object = find_cluster(cluster, connection, datacentername) raise("Cluster #{cluster} cannot be found") if cluster_object.nil? target_hosts = get_cluster_host_utilization(cluster_object) raise("there is no candidate in vcenter that meets all the required conditions, that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory'") if target_hosts.empty? architectures = build_compatible_hosts_lists(target_hosts, percentage) least_used_hosts = select_least_used_hosts(target_hosts, percentage) { 'hosts' => least_used_hosts, 'architectures' => architectures } end end
# File lib/vmpooler/providers/vsphere.rb, line 958 def find_least_used_vpshere_compatible_host(vm) source_host = vm.summary.runtime.host model = get_host_cpu_arch_version(source_host) cluster = source_host.parent target_hosts = get_cluster_host_utilization(cluster, model) raise("There is no host candidate in vcenter that meets all the required conditions, check that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory'") if target_hosts.empty? target_host = target_hosts.min[1] [target_host, target_host.name] end
# File lib/vmpooler/providers/vsphere.rb, line 969 def find_snapshot(vm, snapshotname) get_snapshot_list(vm.snapshot.rootSnapshotList, snapshotname) if vm.snapshot end
# File lib/vmpooler/providers/vsphere.rb, line 1126 def find_template_vm(pool, connection) datacenter = get_target_datacenter_from_config(pool['name']) raise('cannot find datacenter') if datacenter.nil? propSpecs = { # rubocop:disable Naming/VariableName entity: self, inventoryPath: "#{datacenter}/vm/#{pool['template']}" } template_vm_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName raise("Pool #{pool['name']} specifies a template VM of #{pool['template']} which does not exist for the provider #{name}") if template_vm_object.nil? template_vm_object end
# File lib/vmpooler/providers/vsphere.rb, line 980 def find_vm(pool_name, vmname, connection) # Find a VM by its inventory path and return the VM object # Returns nil when a VM, or pool configuration, cannot be found pool_configuration = pool_config(pool_name) return nil if pool_configuration.nil? folder = pool_configuration['folder'] datacenter = get_target_datacenter_from_config(pool_name) return nil if datacenter.nil? propSpecs = { # rubocop:disable Naming/VariableName entity: self, inventoryPath: "#{datacenter}/vm/#{folder}/#{vmname}" } connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName end
Finds a folder object by inventory path Params:
pool_name
-
the pool to find the folder for
connection
-
the vsphere connection object
returns a ManagedObjectReference for the folder found or nil if not found
# File lib/vmpooler/providers/vsphere.rb, line 777 def find_vm_folder(pool_name, connection) # Find a folder by its inventory path and return the object # Returns nil when the object found is not a folder pool_configuration = pool_config(pool_name) return nil if pool_configuration.nil? folder = pool_configuration['folder'] datacenter = get_target_datacenter_from_config(pool_name) return nil if datacenter.nil? propSpecs = { # rubocop:disable Naming/VariableName entity: self, inventoryPath: "#{datacenter}/vm/#{folder}" } folder_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName return nil unless folder_object.instance_of? RbVmomi::VIM::Folder folder_object end
# File lib/vmpooler/providers/vsphere.rb, line 53 def folder_configured?(folder_title, base_folder, configured_folders, whitelist) return true if whitelist&.include?(folder_title) return false unless configured_folders.keys.include?(folder_title) return false unless configured_folders[folder_title] == base_folder true end
Return a hash of VM data Provides vmname, hostname, template, poolname, boottime and powerstate information
# File lib/vmpooler/providers/vsphere.rb, line 553 def generate_vm_hash(vm_object, pool_name) pool_configuration = pool_config(pool_name) return nil if pool_configuration.nil? hostname = vm_object.summary.guest.hostName if vm_object.summary&.guest && vm_object.summary.guest.hostName boottime = vm_object.runtime.bootTime if vm_object.runtime&.bootTime powerstate = vm_object.runtime.powerState if vm_object.runtime&.powerState { 'name' => vm_object.name, 'hostname' => hostname, 'template' => pool_configuration['template'], 'poolname' => pool_name, 'boottime' => boottime, 'powerstate' => powerstate } end
# File lib/vmpooler/providers/vsphere.rb, line 854 def get_average_cluster_utilization(hosts) utilization_counts = hosts.map { |host| host[0] } utilization_counts.inject(:+) / hosts.count end
# File lib/vmpooler/providers/vsphere.rb, line 998 def get_base_vm_container_from(connection) view_manager = connection.serviceContent.viewManager view_manager.CreateContainerView( container: connection.serviceContent.rootFolder, recursive: true, type: ['VirtualMachine'] ) end
# File lib/vmpooler/providers/vsphere.rb, line 949 def get_cluster_host_utilization(cluster, model = nil) cluster_hosts = [] cluster.host.each do |host| host_usage = get_host_utilization(host, model) cluster_hosts << host_usage if host_usage end cluster_hosts end
# File lib/vmpooler/providers/vsphere.rb, line 1158 def get_disk_backing(pool) return :moveChildMostDiskBacking if linked_clone?(pool) :moveAllDiskBackingsAndConsolidate end
# File lib/vmpooler/providers/vsphere.rb, line 138 def get_folder_children(folder_name, connection) folders = [] propSpecs = { # rubocop:disable Naming/VariableName entity: self, inventoryPath: folder_name } folder_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName return folders if folder_object.nil? folder_object.childEntity.each do |folder| next unless folder.is_a? RbVmomi::VIM::Folder folders << { folder.name => folder } end folders end
# File lib/vmpooler/providers/vsphere.rb, line 832 def get_host_cpu_arch_version(host) cpu_model = host.hardware.cpuPkg[0].description cpu_model_parts = cpu_model.split cpu_model_parts[4] end
Returns an array containing cumulative CPU and memory utilization of a host, and its object reference Params:
model
-
CPU arch version to match on
limit
-
Hard limit for CPU or memory utilization beyond which a host is excluded for deployments
returns nil if one on these conditions is true:
the model param is defined and cannot be found the host is in maintenance mode the host status is not 'green' the cpu or memory utilization is bigger than the limit param
# File lib/vmpooler/providers/vsphere.rb, line 807 def get_host_utilization(host, model = nil, limit = 90) limit = @config[:config]['utilization_limit'] if @config[:config].key?('utilization_limit') return nil if model && !host_has_cpu_model?(host, model) return nil if host.runtime.inMaintenanceMode return nil unless host.overallStatus == 'green' return nil unless host.configIssue.empty? cpu_utilization = cpu_utilization_for host memory_utilization = memory_utilization_for host return nil if cpu_utilization.nil? return nil if cpu_utilization.to_d == 0.0.to_d return nil if memory_utilization.nil? return nil if memory_utilization.to_d == 0.0.to_d return nil if cpu_utilization > limit return nil if memory_utilization > limit [cpu_utilization, host] end
# File lib/vmpooler/providers/vsphere.rb, line 1007 def get_snapshot_list(tree, snapshotname) snapshot = nil tree.each do |child| if child.name == snapshotname snapshot ||= child.snapshot else snapshot ||= get_snapshot_list(child.childSnapshotList, snapshotname) end end snapshot end
VSphere
Helper methods
# File lib/vmpooler/providers/vsphere.rb, line 531 def get_target_cluster_from_config(pool_name) pool = pool_config(pool_name) return nil if pool.nil? return pool['clone_target'] unless pool['clone_target'].nil? return global_config[:config]['clone_target'] unless global_config[:config]['clone_target'].nil? nil end
# File lib/vmpooler/providers/vsphere.rb, line 541 def get_target_datacenter_from_config(pool_name) pool = pool_config(pool_name) return nil if pool.nil? return pool['datacenter'] unless pool['datacenter'].nil? return provider_config['datacenter'] unless provider_config['datacenter'].nil? nil end
# File lib/vmpooler/providers/vsphere.rb, line 277 def get_vm(pool_name, vm_name) vm_hash = nil @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) vm_object = find_vm(pool_name, vm_name, connection) return vm_hash if vm_object.nil? vm_hash = generate_vm_hash(vm_object, pool_name) end vm_hash end
# File lib/vmpooler/providers/vsphere.rb, line 1021 def get_vm_details(pool_name, vm_name, connection) vm_object = find_vm(pool_name, vm_name, connection) return nil if vm_object.nil? parent_host_object = vm_object.summary.runtime.host if vm_object.summary&.runtime && vm_object.summary.runtime.host raise('Unable to determine which host the VM is running on') if parent_host_object.nil? parent_host = parent_host_object.name architecture = get_host_cpu_arch_version(parent_host_object) { 'host_name' => parent_host, 'object' => vm_object, 'architecture' => architecture } end
# File lib/vmpooler/providers/vsphere.rb, line 623 def get_vm_folder_path(vm_object) # This gives an array starting from the root Datacenters folder all the way to the VM # [ [Object, String], [Object, String ] ... ] # It's then reversed so that it now goes from the VM to the Datacenter full_path = vm_object.path.reverse # Find the Datacenter object dc_index = full_path.index { |p| p[0].is_a?(RbVmomi::VIM::Datacenter) } return nil if dc_index.nil? # The Datacenter should be at least 2 otherwise there's something # wrong with the array passed in # This is the minimum: # [ VM (0), VM ROOT FOLDER (1), DC (2)] return nil if dc_index <= 1 # Remove the VM name (Starting position of 1 in the slice) # Up until the Root VM Folder of DataCenter Node (dc_index - 2) full_path = full_path.slice(1..dc_index - 2) # Reverse the array back to normal and # then convert the array of paths into a '/' seperated string (full_path.reverse.map { |p| p[1] }).join('/') end
# File lib/vmpooler/providers/vsphere.rb, line 828 def host_has_cpu_model?(host, model) get_host_cpu_arch_version(host) == model end
# File lib/vmpooler/providers/vsphere.rb, line 1164 def linked_clone?(pool) return if pool['create_linked_clone'] == false return true if pool['create_linked_clone'] return true if @config[:config]['create_linked_clones'] end
# File lib/vmpooler/providers/vsphere.rb, line 846 def memory_utilization_for(host) memory_usage = host.summary.quickStats.overallMemoryUsage return nil if memory_usage.nil? memory_size = host.summary.hardware.memorySize / 1024 / 1024 memory_usage.fdiv(memory_size) * 100 end
# File lib/vmpooler/providers/vsphere.rb, line 1045 def migrate_vm(pool_name, vm_name) @connection_pool.with_metrics do |pool_object| begin connection = ensured_vsphere_connection(pool_object) vm_hash = get_vm_details(pool_name, vm_name, connection) @redis.with_metrics do |redis| redis.hset("vmpooler__vm__#{vm_name}", 'host', vm_hash['host_name']) migration_count = redis.scard('vmpooler__migration') migration_limit = @config[:config]['migration_limit'] if @config[:config].key?('migration_limit') if migration_enabled? @config if migration_count >= migration_limit logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}. No migration will be evaluated since the migration_limit has been reached") break end run_select_hosts(pool_name, @provider_hosts) if vm_in_target?(pool_name, vm_hash['host_name'], vm_hash['architecture'], @provider_hosts) logger.log('s', "[ ] [#{pool_name}] No migration required for '#{vm_name}' running on #{vm_hash['host_name']}") else migrate_vm_to_new_host(pool_name, vm_name, vm_hash, connection) end else logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}") end end rescue StandardError logger.log('s', "[!] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}") raise end end end
# File lib/vmpooler/providers/vsphere.rb, line 1096 def migrate_vm_and_record_timing(pool_name, vm_name, vm_hash, target_host_object, dest_host_name) start = Time.now migrate_vm_host(vm_hash['object'], target_host_object) finish = format('%<time>.2f', time: Time.now - start) metrics.timing("migrate.#{pool_name}", finish) metrics.increment("migrate_from.#{vm_hash['host_name']}") metrics.increment("migrate_to.#{dest_host_name}") @redis.with_metrics do |redis| checkout_to_migration = format('%<time>.2f', time: Time.now - Time.parse(redis.hget("vmpooler__vm__#{vm_name}", 'checkout'))) redis.multi redis.hset("vmpooler__vm__#{vm_name}", 'migration_time', finish) redis.hset("vmpooler__vm__#{vm_name}", 'checkout_to_migration', checkout_to_migration) redis.exec end finish end
# File lib/vmpooler/providers/vsphere.rb, line 1113 def migrate_vm_host(vm_object, host) relospec = RbVmomi::VIM.VirtualMachineRelocateSpec(host: host) vm_object.RelocateVM_Task(spec: relospec).wait_for_completion end
# File lib/vmpooler/providers/vsphere.rb, line 1076 def migrate_vm_to_new_host(pool_name, vm_name, vm_hash, connection) @redis.with_metrics do |redis| redis.sadd('vmpooler__migration', vm_name) end target_host_name = select_next_host(pool_name, @provider_hosts, vm_hash['architecture']) target_host_object = find_host_by_dnsname(connection, target_host_name) finish = migrate_vm_and_record_timing(pool_name, vm_name, vm_hash, target_host_object, target_host_name) @redis.with_metrics do |redis| redis.multi redis.hset("vmpooler__vm__#{vm_name}", 'host', target_host_name) redis.hset("vmpooler__vm__#{vm_name}", 'migrated', true) redis.exec end logger.log('s', "[>] [#{pool_name}] '#{vm_name}' migrated from #{vm_hash['host_name']} to #{target_host_name} in #{finish} seconds") ensure @redis.with_metrics do |redis| redis.srem('vmpooler__migration', vm_name) end end
# File lib/vmpooler/providers/vsphere.rb, line 1037 def migration_enabled?(config) migration_limit = config[:config]['migration_limit'] return false unless migration_limit.is_a? Integer return true if migration_limit > 0 false end
name of the provider class
# File lib/vmpooler/providers/vsphere.rb, line 49 def name 'vsphere' end
This should supercede the open_socket
method in the Pool Manager
# File lib/vmpooler/providers/vsphere.rb, line 610 def open_socket(host, domain = nil, timeout = 5, port = 22, &_block) Timeout.timeout(timeout) do target_host = host target_host = "#{host}.#{domain}" if domain sock = TCPSocket.new target_host, port begin yield sock if block_given? ensure sock.close end end end
# File lib/vmpooler/providers/vsphere.rb, line 121 def purge_unconfigured_folders(base_folders, configured_folders, whitelist) @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) base_folders.each do |base_folder| folder_children = get_folder_children(base_folder, connection) next if folder_children.empty? folder_children.each do |folder_hash| folder_hash.each do |folder_title, folder_object| destroy_folder_and_children(folder_object) unless folder_configured?(folder_title, base_folder, configured_folders, whitelist) end end end end end
# File lib/vmpooler/providers/vsphere.rb, line 489 def revert_snapshot(pool_name, vm_name, snapshot_name) @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) vm_object = find_vm(pool_name, vm_name, connection) raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if vm_object.nil? snapshot_object = find_snapshot(vm_object, snapshot_name) raise("Snapshot #{snapshot_name} for VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if snapshot_object.nil? snapshot_object.RevertToSnapshot_Task.wait_for_completion end true end
# File lib/vmpooler/providers/vsphere.rb, line 191 def run_select_hosts(pool_name, target) now = Time.now max_age = @config[:config]['host_selection_max_age'] || 60 loop_delay = 5 datacenter = get_target_datacenter_from_config(pool_name) cluster = get_target_cluster_from_config(pool_name) raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil? raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil? dc = "#{datacenter}_#{cluster}" unless target.key?(dc) select_target_hosts(target, cluster, datacenter) return end wait_for_host_selection(dc, target, loop_delay, max_age) if target[dc].key?('checking') select_target_hosts(target, cluster, datacenter) if target[dc].key?('check_time_finished') && now - target[dc]['check_time_finished'] > max_age end
# File lib/vmpooler/providers/vsphere.rb, line 884 def select_least_used_hosts(hosts, percentage) raise('Provided hosts list to select_least_used_hosts is empty') if hosts.empty? average_utilization = get_average_cluster_utilization(hosts) least_used_hosts = [] hosts.each do |host| least_used_hosts << host if host[0] <= average_utilization end hosts_to_select = (hosts.count * (percentage / 100.0)).to_int hosts_to_select = hosts.count - 1 if percentage == 100 least_used_hosts.sort[0..hosts_to_select].map { |host| host[1].name } end
# File lib/vmpooler/providers/vsphere.rb, line 232 def select_next_host(pool_name, target, architecture = nil) datacenter = get_target_datacenter_from_config(pool_name) cluster = get_target_cluster_from_config(pool_name) raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil? raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil? dc = "#{datacenter}_#{cluster}" @provider_hosts_lock.synchronize do if architecture raise("there is no candidate in vcenter that meets all the required conditions, that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory") unless target[dc].key?('architectures') host = target[dc]['architectures'][architecture].shift target[dc]['architectures'][architecture] << host if target[dc]['hosts'].include?(host) target[dc]['hosts'].delete(host) target[dc]['hosts'] << host end else raise("there is no candidate in vcenter that meets all the required conditions, that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory") unless target[dc].key?('hosts') host = target[dc]['hosts'].shift target[dc]['hosts'] << host target[dc]['architectures'].each do |arch| target[dc]['architectures'][arch] = arch.partition { |v| v != host }.flatten if arch.include?(host) end end return host end end
# File lib/vmpooler/providers/vsphere.rb, line 173 def select_target_hosts(target, cluster, datacenter) percentage = 100 dc = "#{datacenter}_#{cluster}" @provider_hosts_lock.synchronize do begin target[dc] = {} unless target.key?(dc) target[dc]['checking'] = true hosts_hash = find_least_used_hosts(cluster, datacenter, percentage) target[dc] = hosts_hash rescue StandardError target[dc] = {} raise ensure target[dc]['check_time_finished'] = Time.now end end end
# File lib/vmpooler/providers/vsphere.rb, line 409 def set_network_device(datacenter_name, template_vm_network_device, network_name, connection) # Retrieve network object datacenter = connection.serviceInstance.find_datacenter(datacenter_name) new_network = datacenter.network.find { |n| n.name == network_name } raise("Cannot find network #{network_name} in datacenter #{datacenter_name}") unless new_network # Determine network device type # All possible device type options here: https://vdc-download.vmware.com/vmwb-repository/dcr-public/98d63b35-d822-47fe-a87a-ddefd469df06/2e3c7b58-f2bd-486e-8bb1-a75eb0640bee/doc/vim.vm.device.VirtualEthernetCard.html network_device = if template_vm_network_device.instance_of? RbVmomi::VIM::VirtualVmxnet2 RbVmomi::VIM.VirtualVmxnet2 elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualVmxnet3 RbVmomi::VIM.VirtualVmxnet3 elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualE1000 RbVmomi::VIM.VirtualE1000 elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualE1000e RbVmomi::VIM.VirtualE1000e elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualSriovEthernetCard RbVmomi::VIM.VirtualSriovEthernetCard else RbVmomi::VIM.VirtualPCNet32 end # Set up new network device attributes network_device.key = template_vm_network_device.key network_device.deviceInfo = RbVmomi::VIM.Description( label: template_vm_network_device.deviceInfo.label, summary: network_name ) network_device.backing = RbVmomi::VIM.VirtualEthernetCardNetworkBackingInfo( deviceName: network_name, network: new_network, useAutoDetect: false ) network_device.addressType = 'assigned' network_device.connectable = RbVmomi::VIM.VirtualDeviceConnectInfo( allowGuestControl: true, startConnected: true, connected: true ) network_device end
# File lib/vmpooler/providers/vsphere.rb, line 1150 def valid_template_path?(template) return false unless template.include?('/') return false if template[0] == '/' return false if template[-1] == '/' true end
# File lib/vmpooler/providers/vsphere.rb, line 263 def vm_in_target?(pool_name, parent_host, architecture, target) datacenter = get_target_datacenter_from_config(pool_name) cluster = get_target_cluster_from_config(pool_name) raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil? raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil? dc = "#{datacenter}_#{cluster}" raise("there is no candidate in vcenter that meets all the required conditions, that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory") unless target[dc].key?('hosts') return true if target[dc]['hosts'].include?(parent_host) return true if target[dc]['architectures'][architecture].include?(parent_host) false end
# File lib/vmpooler/providers/vsphere.rb, line 519 def vm_ready?(_pool_name, vm_name) begin open_socket(vm_name, global_config[:config]['domain']) rescue StandardError => _e return false end true end
# File lib/vmpooler/providers/vsphere.rb, line 158 def vms_in_pool(pool_name) vms = [] @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) folder_object = find_vm_folder(pool_name, connection) return vms if folder_object.nil? folder_object.childEntity.each do |vm| vms << { 'name' => vm.name } if vm.is_a? RbVmomi::VIM::VirtualMachine end end vms end
# File lib/vmpooler/providers/vsphere.rb, line 581 def vsphere_connection_ok?(connection) _result = connection.serviceInstance.CurrentTime true rescue StandardError false end
# File lib/vmpooler/providers/vsphere.rb, line 209 def wait_for_host_selection(dc, target, maxloop = 0, loop_delay = 1, max_age = 60) loop_count = 1 until target.key?(dc) && target[dc].key?('check_time_finished') sleep(loop_delay) unless maxloop == 0 break if loop_count >= maxloop loop_count += 1 end end return unless target[dc].key?('check_time_finished') loop_count = 1 while Time.now - target[dc]['check_time_finished'] > max_age sleep(loop_delay) unless maxloop == 0 break if loop_count >= maxloop loop_count += 1 end end end