mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 10:08:40 -05:00
Merge pull request #214 from glennsarti/ticket/master/POOLER-73-begin-vsphere-migrate
(POOLER-70)(POOLER-52) Create a functional vSphere Provider
This commit is contained in:
commit
f0f3504c05
7 changed files with 3823 additions and 128 deletions
|
|
@ -51,3 +51,10 @@ Style/WordArray:
|
||||||
# Either sytnax for regex is ok
|
# Either sytnax for regex is ok
|
||||||
Style/RegexpLiteral:
|
Style/RegexpLiteral:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
# In some cases readability is better without these cops enabled
|
||||||
|
Style/ConditionalAssignment:
|
||||||
|
Enabled: false
|
||||||
|
Next:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# This configuration was generated by
|
# This configuration was generated by
|
||||||
# `rubocop --auto-gen-config`
|
# `rubocop --auto-gen-config`
|
||||||
# on 2017-03-16 15:37:18 -0700 using RuboCop version 0.47.1.
|
# on 2017-03-30 17:30:59 -0700 using RuboCop version 0.47.1.
|
||||||
# The point is for the user to remove these configuration records
|
# The point is for the user to remove these configuration records
|
||||||
# one by one as the offenses are removed from the code base.
|
# one by one as the offenses are removed from the code base.
|
||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
|
|
@ -56,6 +56,11 @@ Performance/RedundantMatch:
|
||||||
- 'lib/vmpooler/api/v1.rb'
|
- 'lib/vmpooler/api/v1.rb'
|
||||||
- 'lib/vmpooler/vsphere_helper.rb'
|
- 'lib/vmpooler/vsphere_helper.rb'
|
||||||
|
|
||||||
|
# Offense count: 1
|
||||||
|
Style/AccessorMethodName:
|
||||||
|
Exclude:
|
||||||
|
- 'lib/vmpooler/providers/vsphere.rb'
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
|
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
|
||||||
|
|
@ -97,7 +102,7 @@ Style/CaseEquality:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/vmpooler/api/helpers.rb'
|
- 'lib/vmpooler/api/helpers.rb'
|
||||||
|
|
||||||
# Offense count: 13
|
# Offense count: 12
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentOneStep, IndentationWidth.
|
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentOneStep, IndentationWidth.
|
||||||
# SupportedStyles: case, end
|
# SupportedStyles: case, end
|
||||||
|
|
@ -115,9 +120,7 @@ Style/ClosingParenthesisIndentation:
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions.
|
Style/EmptyCaseCondition:
|
||||||
# SupportedStyles: assign_to_condition, assign_inside_condition
|
|
||||||
Style/ConditionalAssignment:
|
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/vmpooler/vsphere_helper.rb'
|
- 'lib/vmpooler/vsphere_helper.rb'
|
||||||
|
|
||||||
|
|
@ -173,7 +176,7 @@ Style/FormatString:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/vmpooler/pool_manager.rb'
|
- 'lib/vmpooler/pool_manager.rb'
|
||||||
|
|
||||||
# Offense count: 9
|
# Offense count: 10
|
||||||
# Configuration parameters: MinBodyLength.
|
# Configuration parameters: MinBodyLength.
|
||||||
Style/GuardClause:
|
Style/GuardClause:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
|
@ -272,18 +275,6 @@ Style/NegatedIf:
|
||||||
- 'lib/vmpooler/api/v1.rb'
|
- 'lib/vmpooler/api/v1.rb'
|
||||||
- 'lib/vmpooler/pool_manager.rb'
|
- 'lib/vmpooler/pool_manager.rb'
|
||||||
|
|
||||||
# Offense count: 12
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
|
|
||||||
# SupportedStyles: skip_modifier_ifs, always
|
|
||||||
Style/Next:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/vmpooler/api/dashboard.rb'
|
|
||||||
- 'lib/vmpooler/api/helpers.rb'
|
|
||||||
- 'lib/vmpooler/api/v1.rb'
|
|
||||||
- 'lib/vmpooler/pool_manager.rb'
|
|
||||||
- 'lib/vmpooler/vsphere_helper.rb'
|
|
||||||
|
|
||||||
# Offense count: 3
|
# Offense count: 3
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
Style/Not:
|
Style/Not:
|
||||||
|
|
@ -337,6 +328,12 @@ Style/RedundantBegin:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/vmpooler/pool_manager.rb'
|
- 'lib/vmpooler/pool_manager.rb'
|
||||||
|
|
||||||
|
# Offense count: 2
|
||||||
|
# Cop supports --auto-correct.
|
||||||
|
Style/RedundantException:
|
||||||
|
Exclude:
|
||||||
|
- 'lib/vmpooler/vsphere_helper.rb'
|
||||||
|
|
||||||
# Offense count: 26
|
# Offense count: 26
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
Style/RedundantParentheses:
|
Style/RedundantParentheses:
|
||||||
|
|
@ -452,14 +449,6 @@ Style/VariableName:
|
||||||
- 'lib/vmpooler/pool_manager.rb'
|
- 'lib/vmpooler/pool_manager.rb'
|
||||||
- 'lib/vmpooler/vsphere_helper.rb'
|
- 'lib/vmpooler/vsphere_helper.rb'
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: SupportedStyles, WordRegex.
|
|
||||||
# SupportedStyles: percent, brackets
|
|
||||||
Style/WordArray:
|
|
||||||
EnforcedStyle: percent
|
|
||||||
MinSize: -Infinity
|
|
||||||
|
|
||||||
# Offense count: 2
|
# Offense count: 2
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
Style/ZeroLengthPredicate:
|
Style/ZeroLengthPredicate:
|
||||||
|
|
|
||||||
|
|
@ -4,101 +4,191 @@ module Vmpooler
|
||||||
class Base
|
class Base
|
||||||
# These defs must be overidden in child classes
|
# These defs must be overidden in child classes
|
||||||
|
|
||||||
def initialize(options)
|
# Helper Methods
|
||||||
|
# Global Logger object
|
||||||
|
attr_reader :logger
|
||||||
|
# Global Metrics object
|
||||||
|
attr_reader :metrics
|
||||||
|
# Provider options passed in during initialization
|
||||||
|
attr_reader :provider_options
|
||||||
|
|
||||||
|
def initialize(config, logger, metrics, name, options)
|
||||||
|
@config = config
|
||||||
|
@logger = logger
|
||||||
|
@metrics = metrics
|
||||||
|
@provider_name = name
|
||||||
|
|
||||||
@provider_options = options
|
@provider_options = options
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns
|
# Helper Methods
|
||||||
# [String] Name of the provider service
|
|
||||||
def name
|
|
||||||
'base'
|
|
||||||
end
|
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# pool : hashtable from config file
|
# [String] pool_name : Name of the pool to get the configuration
|
||||||
# returns
|
# returns
|
||||||
# hashtable
|
# [Hashtable] : The pools configuration from the config file. Returns nil if the pool does not exist
|
||||||
# name : name of the device <---- TODO is this all?
|
def pool_config(pool_name)
|
||||||
def vms_in_pool(_pool)
|
# Get the configuration of a specific pool
|
||||||
|
@config[:pools].each do |pool|
|
||||||
|
return pool if pool['name'] == pool_name
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# returns
|
||||||
|
# [Hashtable] : This provider's configuration from the config file. Returns nil if the provider does not exist
|
||||||
|
def provider_config
|
||||||
|
@config[:providers].each do |provider|
|
||||||
|
# Convert the symbol from the config into a string for comparison
|
||||||
|
return provider[1] if provider[0].to_s == @provider_name
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# returns
|
||||||
|
# [Hashtable] : The entire VMPooler configuration
|
||||||
|
def global_config
|
||||||
|
# This entire VM Pooler config
|
||||||
|
@config
|
||||||
|
end
|
||||||
|
|
||||||
|
# returns
|
||||||
|
# [String] : Name of the provider service
|
||||||
|
def name
|
||||||
|
@provider_name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pool Manager Methods
|
||||||
|
|
||||||
|
# inputs
|
||||||
|
# [String] pool_name : Name of the pool
|
||||||
|
# returns
|
||||||
|
# Array[Hashtable]
|
||||||
|
# Hash contains:
|
||||||
|
# 'name' => [String] Name of VM
|
||||||
|
def vms_in_pool(_pool_name)
|
||||||
raise("#{self.class.name} does not implement vms_in_pool")
|
raise("#{self.class.name} does not implement vms_in_pool")
|
||||||
end
|
end
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# vm_name: string
|
# [String]pool_name : Name of the pool
|
||||||
|
# [String] vm_name : Name of the VM
|
||||||
# returns
|
# returns
|
||||||
# [String] hostname = Name of the host computer running the vm. If this is not a Virtual Machine, it returns the vm_name
|
# [String] : Name of the host computer running the vm. If this is not a Virtual Machine, it returns the vm_name
|
||||||
def get_vm_host(_vm_name)
|
def get_vm_host(_pool_name, _vm_name)
|
||||||
raise("#{self.class.name} does not implement get_vm_host")
|
raise("#{self.class.name} does not implement get_vm_host")
|
||||||
end
|
end
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# vm_name: string
|
# [String] pool_name : Name of the pool
|
||||||
|
# [String] vm_name : Name of the VM
|
||||||
# returns
|
# returns
|
||||||
# [String] hostname = Name of the most appropriate host computer to run this VM. Useful for load balancing VMs in a cluster
|
# [String] : Name of the most appropriate host computer to run this VM. Useful for load balancing VMs in a cluster
|
||||||
# If this is not a Virtual Machine, it returns the vm_name
|
# If this is not a Virtual Machine, it returns the vm_name
|
||||||
def find_least_used_compatible_host(_vm_name)
|
def find_least_used_compatible_host(_pool_name, _vm_name)
|
||||||
raise("#{self.class.name} does not implement find_least_used_compatible_host")
|
raise("#{self.class.name} does not implement find_least_used_compatible_host")
|
||||||
end
|
end
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# vm_name: string
|
# [String] pool_name : Name of the pool
|
||||||
# dest_host_name: string (Name of the host to migrate `vm_name` to)
|
# [String] vm_name : Name of the VM to migrate
|
||||||
|
# [String] dest_host_name : Name of the host to migrate `vm_name` to
|
||||||
# returns
|
# returns
|
||||||
# [Boolean] Returns true on success or false on failure
|
# [Boolean] : true on success or false on failure
|
||||||
def migrate_vm_to_host(_vm_name, _dest_host_name)
|
def migrate_vm_to_host(_pool_name, _vm_name, _dest_host_name)
|
||||||
raise("#{self.class.name} does not implement migrate_vm_to_host")
|
raise("#{self.class.name} does not implement migrate_vm_to_host")
|
||||||
end
|
end
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# vm_name: string
|
# [String] pool_name : Name of the pool
|
||||||
|
# [String] vm_name : Name of the VM to find
|
||||||
# returns
|
# returns
|
||||||
# nil if it doesn't exist
|
# nil if VM doesn't exist
|
||||||
# Hastable of the VM
|
# [Hastable] of the VM
|
||||||
# [String] name = Name of the VM
|
# [String] name : Name of the VM
|
||||||
# [String] hostname = Name reported by Vmware tools (host.summary.guest.hostName)
|
# [String] hostname : Name reported by Vmware tools (host.summary.guest.hostName)
|
||||||
# [String] template = This is the name of template exposed by the API. It must _match_ the poolname
|
# [String] template : This is the name of template exposed by the API. It must _match_ the poolname
|
||||||
# [String] poolname = Name of the pool the VM is located
|
# [String] poolname : Name of the pool the VM is located
|
||||||
# [Time] boottime = Time when the VM was created/booted
|
# [Time] boottime : Time when the VM was created/booted
|
||||||
# [String] powerstate = Current power state of a VM. Valid values (as per vCenter API)
|
# [String] powerstate : Current power state of a VM. Valid values (as per vCenter API)
|
||||||
# - 'PoweredOn','PoweredOff'
|
# - 'PoweredOn','PoweredOff'
|
||||||
def get_vm(_vm_name)
|
def get_vm(_pool_name, _vm_name)
|
||||||
raise("#{self.class.name} does not implement get_vm")
|
raise("#{self.class.name} does not implement get_vm")
|
||||||
end
|
end
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# pool : hashtable from config file
|
# [String] pool : Name of the pool
|
||||||
# new_vmname : string Name the new VM should use
|
# [String] new_vmname : Name to give the new VM
|
||||||
# returns
|
# returns
|
||||||
# Hashtable of the VM as per get_vm
|
# [Hashtable] of the VM as per get_vm
|
||||||
def create_vm(_pool, _new_vmname)
|
# Raises RuntimeError if the pool_name is not supported by the Provider
|
||||||
|
def create_vm(_pool_name, _new_vmname)
|
||||||
raise("#{self.class.name} does not implement create_vm")
|
raise("#{self.class.name} does not implement create_vm")
|
||||||
end
|
end
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# vm_name: string
|
# [String] pool_name : Name of the pool
|
||||||
# pool: string
|
# [String] vm_name : Name of the VM to create the disk on
|
||||||
|
# [Integer] disk_size : Size of the disk to create in Gigabytes (GB)
|
||||||
# returns
|
# returns
|
||||||
# boolean : true if success, false on error
|
# [Boolean] : true if success, false if disk could not be created
|
||||||
def destroy_vm(_vm_name, _pool)
|
# Raises RuntimeError if the Pool does not exist
|
||||||
|
# Raises RuntimeError if the VM does not exist
|
||||||
|
def create_disk(_pool_name, _vm_name, _disk_size)
|
||||||
|
raise("#{self.class.name} does not implement create_disk")
|
||||||
|
end
|
||||||
|
|
||||||
|
# inputs
|
||||||
|
# [String] pool_name : Name of the pool
|
||||||
|
# [String] new_vmname : Name of the VM to create the snapshot on
|
||||||
|
# [String] new_snapshot_name : Name of the new snapshot to create
|
||||||
|
# returns
|
||||||
|
# [Boolean] : true if success, false if snapshot could not be created
|
||||||
|
# Raises RuntimeError if the VM does not exist
|
||||||
|
# Raises RuntimeError if the snapshot already exists
|
||||||
|
def create_snapshot(_pool_name, _vm_name, _new_snapshot_name)
|
||||||
|
raise("#{self.class.name} does not implement create_snapshot")
|
||||||
|
end
|
||||||
|
|
||||||
|
# inputs
|
||||||
|
# [String] pool_name : Name of the pool
|
||||||
|
# [String] new_vmname : Name of the VM to restore
|
||||||
|
# [String] snapshot_name : Name of the snapshot to restore to
|
||||||
|
# returns
|
||||||
|
# [Boolean] : true if success, false if snapshot could not be revertted
|
||||||
|
# Raises RuntimeError if the VM does not exist
|
||||||
|
# Raises RuntimeError if the snapshot already exists
|
||||||
|
def revert_snapshot(_pool_name, _vm_name, _snapshot_name)
|
||||||
|
raise("#{self.class.name} does not implement revert_snapshot")
|
||||||
|
end
|
||||||
|
|
||||||
|
# inputs
|
||||||
|
# [String] pool_name : Name of the pool
|
||||||
|
# [String] vm_name : Name of the VM to destroy
|
||||||
|
# returns
|
||||||
|
# [Boolean] : true if success, false on error. Should returns true if the VM is missing
|
||||||
|
def destroy_vm(_pool_name, _vm_name)
|
||||||
raise("#{self.class.name} does not implement destroy_vm")
|
raise("#{self.class.name} does not implement destroy_vm")
|
||||||
end
|
end
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# vm : string
|
# [String] pool_name : Name of the pool
|
||||||
# pool: string
|
# [String] vm_name : Name of the VM to check if ready
|
||||||
# timeout: int (Seconds)
|
|
||||||
# returns
|
# returns
|
||||||
# result: boolean
|
# [Boolean] : true if ready, false if not
|
||||||
def vm_ready?(_vm, _pool, _timeout)
|
def vm_ready?(_pool_name, _vm_name)
|
||||||
raise("#{self.class.name} does not implement vm_ready?")
|
raise("#{self.class.name} does not implement vm_ready?")
|
||||||
end
|
end
|
||||||
|
|
||||||
# inputs
|
# inputs
|
||||||
# vm : string
|
# [String] pool_name : Name of the pool
|
||||||
|
# [String] vm_name : Name of the VM to check if it exists
|
||||||
# returns
|
# returns
|
||||||
# result: boolean
|
# [Boolean] : true if it exists, false if not
|
||||||
def vm_exists?(vm)
|
def vm_exists?(pool_name, vm_name)
|
||||||
!get_vm(vm).nil?
|
!get_vm(pool_name, vm_name).nil?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,687 @@ module Vmpooler
|
||||||
class PoolManager
|
class PoolManager
|
||||||
class Provider
|
class Provider
|
||||||
class VSphere < Vmpooler::PoolManager::Provider::Base
|
class VSphere < Vmpooler::PoolManager::Provider::Base
|
||||||
def initialize(options)
|
def initialize(config, logger, metrics, name, options)
|
||||||
super(options)
|
super(config, logger, metrics, name, options)
|
||||||
|
@credentials = provider_config
|
||||||
|
@conf = global_config[:config]
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
'vsphere'
|
'vsphere'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vms_in_pool(pool_name)
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
foldername = pool_config(pool_name)['folder']
|
||||||
|
folder_object = find_folder(foldername, connection)
|
||||||
|
|
||||||
|
vms = []
|
||||||
|
|
||||||
|
return vms if folder_object.nil?
|
||||||
|
|
||||||
|
folder_object.childEntity.each do |vm|
|
||||||
|
vms << { 'name' => vm.name }
|
||||||
|
end
|
||||||
|
|
||||||
|
vms
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_vm_host(_pool_name, vm_name)
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
vm_object = find_vm(vm_name, connection)
|
||||||
|
return nil if vm_object.nil?
|
||||||
|
|
||||||
|
host_name = nil
|
||||||
|
host_name = vm_object.summary.runtime.host.name if vm_object.summary && vm_object.summary.runtime && vm_object.summary.runtime.host
|
||||||
|
|
||||||
|
host_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_least_used_compatible_host(_pool_name, vm_name)
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
vm_object = find_vm(vm_name, connection)
|
||||||
|
|
||||||
|
return nil if vm_object.nil?
|
||||||
|
host_object = find_least_used_vpshere_compatible_host(vm_object)
|
||||||
|
|
||||||
|
return nil if host_object.nil?
|
||||||
|
host_object[0].name
|
||||||
|
end
|
||||||
|
|
||||||
|
def migrate_vm_to_host(pool_name, vm_name, dest_host_name)
|
||||||
|
pool = pool_config(pool_name)
|
||||||
|
raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil?
|
||||||
|
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
vm_object = find_vm(vm_name, connection)
|
||||||
|
raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
|
||||||
|
|
||||||
|
target_cluster_name = get_target_cluster_from_config(pool_name)
|
||||||
|
cluster = find_cluster(target_cluster_name, connection)
|
||||||
|
raise("Pool #{pool_name} specifies cluster #{target_cluster_name} which does not exist for the provider #{name}") if cluster.nil?
|
||||||
|
|
||||||
|
# Go through each host and initiate a migration when the correct host name is found
|
||||||
|
cluster.host.each do |host|
|
||||||
|
if host.name == dest_host_name
|
||||||
|
migrate_vm_host(vm_object, host)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_vm(_pool_name, vm_name)
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
vm_object = find_vm(vm_name, connection)
|
||||||
|
return nil if vm_object.nil?
|
||||||
|
|
||||||
|
vm_folder_path = get_vm_folder_path(vm_object)
|
||||||
|
# Find the pool name based on the folder path
|
||||||
|
pool_name = nil
|
||||||
|
template_name = nil
|
||||||
|
global_config[:pools].each do |pool|
|
||||||
|
if pool['folder'] == vm_folder_path
|
||||||
|
pool_name = pool['name']
|
||||||
|
template_name = pool['template']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
generate_vm_hash(vm_object, template_name, pool_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
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?
|
||||||
|
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
# Assume all pool config is valid i.e. not missing
|
||||||
|
template_path = pool['template']
|
||||||
|
target_folder_path = pool['folder']
|
||||||
|
target_datastore = pool['datastore']
|
||||||
|
target_cluster_name = get_target_cluster_from_config(pool_name)
|
||||||
|
|
||||||
|
# Extract the template VM name from the full path
|
||||||
|
raise("Pool #{pool_name} did specify a full path for the template for the provider #{name}") unless template_path =~ /\//
|
||||||
|
templatefolders = template_path.split('/')
|
||||||
|
template_name = templatefolders.pop
|
||||||
|
|
||||||
|
# Get the actual objects from vSphere
|
||||||
|
template_folder_object = find_folder(templatefolders.join('/'), connection)
|
||||||
|
raise("Pool #{pool_name} specifies a template folder of #{templatefolders.join('/')} which does not exist for the provider #{name}") if template_folder_object.nil?
|
||||||
|
|
||||||
|
template_vm_object = template_folder_object.find(template_name)
|
||||||
|
raise("Pool #{pool_name} specifies a template VM of #{template_name} which does not exist for the provider #{name}") if template_vm_object.nil?
|
||||||
|
|
||||||
|
# Annotate with creation time, origin template, etc.
|
||||||
|
# Add extraconfig options that can be queried by vmtools
|
||||||
|
config_spec = RbVmomi::VIM.VirtualMachineConfigSpec(
|
||||||
|
annotation: JSON.pretty_generate(
|
||||||
|
name: new_vmname,
|
||||||
|
created_by: provider_config['username'],
|
||||||
|
base_template: template_path,
|
||||||
|
creation_timestamp: Time.now.utc
|
||||||
|
),
|
||||||
|
extraConfig: [
|
||||||
|
{ key: 'guestinfo.hostname', value: new_vmname }
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Choose a cluster/host to place the new VM on
|
||||||
|
target_host_object = find_least_used_host(target_cluster_name, connection)
|
||||||
|
|
||||||
|
# Put the VM in the specified folder and resource pool
|
||||||
|
relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(
|
||||||
|
datastore: find_datastore(target_datastore, connection),
|
||||||
|
host: target_host_object,
|
||||||
|
diskMoveType: :moveChildMostDiskBacking
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a clone spec
|
||||||
|
clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(
|
||||||
|
location: relocate_spec,
|
||||||
|
config: config_spec,
|
||||||
|
powerOn: true,
|
||||||
|
template: false
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the new VM
|
||||||
|
new_vm_object = template_vm_object.CloneVM_Task(
|
||||||
|
folder: find_folder(target_folder_path, connection),
|
||||||
|
name: new_vmname,
|
||||||
|
spec: clone_spec
|
||||||
|
).wait_for_completion
|
||||||
|
|
||||||
|
generate_vm_hash(new_vm_object, template_path, pool_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
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 = get_connection
|
||||||
|
|
||||||
|
vm_object = find_vm(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)
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_snapshot(pool_name, vm_name, new_snapshot_name)
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
vm_object = find_vm(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
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def revert_snapshot(pool_name, vm_name, snapshot_name)
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
vm_object = find_vm(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
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_vm(_pool_name, vm_name)
|
||||||
|
connection = get_connection
|
||||||
|
|
||||||
|
vm_object = find_vm(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 && vm_object.runtime.powerState && vm_object.runtime.powerState == 'poweredOn'
|
||||||
|
|
||||||
|
# Kill it with fire
|
||||||
|
vm_object.Destroy_Task.wait_for_completion
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def vm_ready?(_pool_name, vm_name)
|
||||||
|
begin
|
||||||
|
open_socket(vm_name)
|
||||||
|
rescue => _err
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def provider_config
|
||||||
|
# The vSphere configuration is currently in it's own root. This will
|
||||||
|
# eventually shift into the same location base expects it
|
||||||
|
global_config[:vsphere]
|
||||||
|
end
|
||||||
|
|
||||||
|
# VSphere Helper methods
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def generate_vm_hash(vm_object, template_name, pool_name)
|
||||||
|
hash = { 'name' => nil, 'hostname' => nil, 'template' => nil, 'poolname' => nil, 'boottime' => nil, 'powerstate' => nil }
|
||||||
|
|
||||||
|
hash['name'] = vm_object.name
|
||||||
|
hash['hostname'] = vm_object.summary.guest.hostName if vm_object.summary && vm_object.summary.guest && vm_object.summary.guest.hostName
|
||||||
|
hash['template'] = template_name
|
||||||
|
hash['poolname'] = pool_name
|
||||||
|
hash['boottime'] = vm_object.runtime.bootTime if vm_object.runtime && vm_object.runtime.bootTime
|
||||||
|
hash['powerstate'] = vm_object.runtime.powerState if vm_object.runtime && vm_object.runtime.powerState
|
||||||
|
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
|
||||||
|
# vSphere helper methods
|
||||||
|
ADAPTER_TYPE = 'lsiLogic'.freeze
|
||||||
|
DISK_TYPE = 'thin'.freeze
|
||||||
|
DISK_MODE = 'persistent'.freeze
|
||||||
|
|
||||||
|
def get_connection
|
||||||
|
begin
|
||||||
|
@connection.serviceInstance.CurrentTime
|
||||||
|
rescue
|
||||||
|
@connection = connect_to_vsphere @credentials
|
||||||
|
end
|
||||||
|
|
||||||
|
@connection
|
||||||
|
end
|
||||||
|
|
||||||
|
def connect_to_vsphere(credentials)
|
||||||
|
max_tries = @conf['max_tries'] || 3
|
||||||
|
retry_factor = @conf['retry_factor'] || 10
|
||||||
|
try = 1
|
||||||
|
begin
|
||||||
|
connection = RbVmomi::VIM.connect host: credentials['server'],
|
||||||
|
user: credentials['username'],
|
||||||
|
password: credentials['password'],
|
||||||
|
insecure: credentials['insecure'] || true
|
||||||
|
metrics.increment('connect.open')
|
||||||
|
return connection
|
||||||
|
rescue => err
|
||||||
|
try += 1
|
||||||
|
metrics.increment('connect.fail')
|
||||||
|
raise err if try == max_tries
|
||||||
|
sleep(try * retry_factor)
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This should supercede the open_socket method in the Pool Manager
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def add_disk(vm, size, datastore, connection)
|
||||||
|
return false unless size.to_i > 0
|
||||||
|
|
||||||
|
vmdk_datastore = find_datastore(datastore, connection)
|
||||||
|
vmdk_file_name = "#{vm['name']}/#{vm['name']}_#{find_vmdks(vm['name'], datastore, connection).length + 1}.vmdk"
|
||||||
|
|
||||||
|
controller = find_disk_controller(vm)
|
||||||
|
|
||||||
|
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: "[#{vmdk_datastore.name}] #{vmdk_file_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
device = RbVmomi::VIM::VirtualDisk(
|
||||||
|
backing: vmdk_backing,
|
||||||
|
capacityInKB: size.to_i * 1024 * 1024,
|
||||||
|
controllerKey: controller.key,
|
||||||
|
key: -1,
|
||||||
|
unitNumber: find_disk_unit_number(vm, controller)
|
||||||
|
)
|
||||||
|
|
||||||
|
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: connection.serviceInstance.find_datacenter,
|
||||||
|
name: "[#{vmdk_datastore.name}] #{vmdk_file_name}",
|
||||||
|
spec: vmdk_spec
|
||||||
|
).wait_for_completion
|
||||||
|
|
||||||
|
vm.ReconfigVM_Task(spec: vm_config_spec).wait_for_completion
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_datastore(datastorename, connection)
|
||||||
|
datacenter = connection.serviceInstance.find_datacenter
|
||||||
|
datacenter.find_datastore(datastorename)
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_device(vm, device_name)
|
||||||
|
vm.config.hardware.device.each do |device|
|
||||||
|
return device if device.deviceInfo.label == device_name
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_disk_controller(vm)
|
||||||
|
devices = find_disk_devices(vm)
|
||||||
|
|
||||||
|
devices.keys.sort.each do |device|
|
||||||
|
if devices[device]['children'].length < 15
|
||||||
|
return find_device(vm, devices[device]['device'].deviceInfo.label)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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|
|
||||||
|
if used_unit_numbers.grep(scsi_id).length <= 0
|
||||||
|
available_unit_numbers.push(scsi_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
available_unit_numbers.sort[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_folder(foldername, connection)
|
||||||
|
datacenter = connection.serviceInstance.find_datacenter
|
||||||
|
base = datacenter.vmFolder
|
||||||
|
|
||||||
|
folders = foldername.split('/')
|
||||||
|
folders.each do |folder|
|
||||||
|
raise("Unexpected object type encountered (#{base.class}) while finding folder") unless base.is_a? RbVmomi::VIM::Folder
|
||||||
|
base = base.childEntity.find { |f| f.name == folder }
|
||||||
|
end
|
||||||
|
|
||||||
|
base
|
||||||
|
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
|
||||||
|
def get_host_utilization(host, model = nil, limit = 90)
|
||||||
|
if model
|
||||||
|
return nil unless host_has_cpu_model?(host, model)
|
||||||
|
end
|
||||||
|
return nil if host.runtime.inMaintenanceMode
|
||||||
|
return nil unless host.overallStatus == 'green'
|
||||||
|
|
||||||
|
cpu_utilization = cpu_utilization_for host
|
||||||
|
memory_utilization = memory_utilization_for host
|
||||||
|
|
||||||
|
return nil if cpu_utilization > limit
|
||||||
|
return nil if memory_utilization > limit
|
||||||
|
|
||||||
|
[cpu_utilization + memory_utilization, host]
|
||||||
|
end
|
||||||
|
|
||||||
|
def host_has_cpu_model?(host, model)
|
||||||
|
get_host_cpu_arch_version(host) == model
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_host_cpu_arch_version(host)
|
||||||
|
cpu_model = host.hardware.cpuPkg[0].description
|
||||||
|
cpu_model_parts = cpu_model.split
|
||||||
|
arch_version = cpu_model_parts[4]
|
||||||
|
arch_version
|
||||||
|
end
|
||||||
|
|
||||||
|
def cpu_utilization_for(host)
|
||||||
|
cpu_usage = host.summary.quickStats.overallCpuUsage
|
||||||
|
cpu_size = host.summary.hardware.cpuMhz * host.summary.hardware.numCpuCores
|
||||||
|
(cpu_usage.to_f / cpu_size.to_f) * 100
|
||||||
|
end
|
||||||
|
|
||||||
|
def memory_utilization_for(host)
|
||||||
|
memory_usage = host.summary.quickStats.overallMemoryUsage
|
||||||
|
memory_size = host.summary.hardware.memorySize / 1024 / 1024
|
||||||
|
(memory_usage.to_f / memory_size.to_f) * 100
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_least_used_host(cluster, connection)
|
||||||
|
cluster_object = find_cluster(cluster, connection)
|
||||||
|
target_hosts = get_cluster_host_utilization(cluster_object)
|
||||||
|
least_used_host = target_hosts.sort[0][1]
|
||||||
|
least_used_host
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_cluster(cluster, connection)
|
||||||
|
datacenter = connection.serviceInstance.find_datacenter
|
||||||
|
datacenter.hostFolder.children.find { |cluster_object| cluster_object.name == cluster }
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_cluster_host_utilization(cluster)
|
||||||
|
cluster_hosts = []
|
||||||
|
cluster.host.each do |host|
|
||||||
|
host_usage = get_host_utilization(host)
|
||||||
|
cluster_hosts << host_usage if host_usage
|
||||||
|
end
|
||||||
|
cluster_hosts
|
||||||
|
end
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
cluster.host.each do |host|
|
||||||
|
host_usage = get_host_utilization(host, model)
|
||||||
|
target_hosts << host_usage if host_usage
|
||||||
|
end
|
||||||
|
target_host = target_hosts.sort[0][1]
|
||||||
|
[target_host, target_host.name]
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_pool(poolname, connection)
|
||||||
|
datacenter = connection.serviceInstance.find_datacenter
|
||||||
|
base = datacenter.hostFolder
|
||||||
|
pools = poolname.split('/')
|
||||||
|
pools.each do |pool|
|
||||||
|
if base.is_a?(RbVmomi::VIM::Folder)
|
||||||
|
base = base.childEntity.find { |f| f.name == pool }
|
||||||
|
elsif base.is_a?(RbVmomi::VIM::ClusterComputeResource)
|
||||||
|
base = base.resourcePool.resourcePool.find { |f| f.name == pool }
|
||||||
|
elsif base.is_a?(RbVmomi::VIM::ResourcePool)
|
||||||
|
base = base.resourcePool.find { |f| f.name == pool }
|
||||||
|
else
|
||||||
|
raise("Unexpected object type encountered (#{base.class}) while finding resource pool")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
base = base.resourcePool unless base.is_a?(RbVmomi::VIM::ResourcePool) && base.respond_to?(:resourcePool)
|
||||||
|
base
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_snapshot(vm, snapshotname)
|
||||||
|
get_snapshot_list(vm.snapshot.rootSnapshotList, snapshotname) if vm.snapshot
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_vm(vmname, connection)
|
||||||
|
find_vm_light(vmname, connection) || find_vm_heavy(vmname, connection)[vmname]
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_vm_light(vmname, connection)
|
||||||
|
connection.searchIndex.FindByDnsName(vmSearch: true, dnsName: vmname)
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_vm_heavy(vmname, connection)
|
||||||
|
vmname = vmname.is_a?(Array) ? vmname : [vmname]
|
||||||
|
container_view = get_base_vm_container_from(connection)
|
||||||
|
property_collector = connection.propertyCollector
|
||||||
|
|
||||||
|
object_set = [{
|
||||||
|
obj: container_view,
|
||||||
|
skip: true,
|
||||||
|
selectSet: [RbVmomi::VIM::TraversalSpec.new(
|
||||||
|
name: 'gettingTheVMs',
|
||||||
|
path: 'view',
|
||||||
|
skip: false,
|
||||||
|
type: 'ContainerView'
|
||||||
|
)]
|
||||||
|
}]
|
||||||
|
|
||||||
|
prop_set = [{
|
||||||
|
pathSet: ['name'],
|
||||||
|
type: 'VirtualMachine'
|
||||||
|
}]
|
||||||
|
|
||||||
|
results = property_collector.RetrievePropertiesEx(
|
||||||
|
specSet: [{
|
||||||
|
objectSet: object_set,
|
||||||
|
propSet: prop_set
|
||||||
|
}],
|
||||||
|
options: { maxObjects: nil }
|
||||||
|
)
|
||||||
|
|
||||||
|
vms = {}
|
||||||
|
results.objects.each do |result|
|
||||||
|
name = result.propSet.first.val
|
||||||
|
next unless vmname.include? name
|
||||||
|
vms[name] = result.obj
|
||||||
|
end
|
||||||
|
|
||||||
|
while results.token
|
||||||
|
results = property_collector.ContinueRetrievePropertiesEx(token: results.token)
|
||||||
|
results.objects.each do |result|
|
||||||
|
name = result.propSet.first.val
|
||||||
|
next unless vmname.include? name
|
||||||
|
vms[name] = result.obj
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
vms
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_vmdks(vmname, datastore, connection)
|
||||||
|
disks = []
|
||||||
|
|
||||||
|
vmdk_datastore = find_datastore(datastore, connection)
|
||||||
|
|
||||||
|
vm_files = vmdk_datastore._connection.serviceContent.propertyCollector.collectMultiple vmdk_datastore.vm, 'layoutEx.file'
|
||||||
|
vm_files.keys.each do |f|
|
||||||
|
vm_files[f]['layoutEx.file'].each do |l|
|
||||||
|
if l.name =~ /^\[#{vmdk_datastore.name}\] #{vmname}\/#{vmname}_([0-9]+).vmdk/
|
||||||
|
disks.push(l)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
disks
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_base_vm_container_from(connection)
|
||||||
|
view_manager = connection.serviceContent.viewManager
|
||||||
|
view_manager.CreateContainerView(
|
||||||
|
container: connection.serviceContent.rootFolder,
|
||||||
|
recursive: true,
|
||||||
|
type: ['VirtualMachine']
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def migrate_vm_host(vm, host)
|
||||||
|
relospec = RbVmomi::VIM.VirtualMachineRelocateSpec(host: host)
|
||||||
|
vm.RelocateVM_Task(spec: relospec).wait_for_completion
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
@connection.close
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,22 @@ MockFolder = Struct.new(
|
||||||
def children
|
def children
|
||||||
childEntity
|
childEntity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# https://github.com/vmware/rbvmomi/blob/master/lib/rbvmomi/vim/Folder.rb#L9-L12
|
||||||
|
def find(name, type=Object)
|
||||||
|
# Fake the searchIndex
|
||||||
|
childEntity.each do |child|
|
||||||
|
if child.name == name
|
||||||
|
if child.kind_of?(type)
|
||||||
|
return child
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
MockHostSystem = Struct.new(
|
MockHostSystem = Struct.new(
|
||||||
|
|
@ -103,7 +119,7 @@ MockServiceInstance = Struct.new(
|
||||||
# In our mocked instance, DataCenters are always in the root Folder.
|
# In our mocked instance, DataCenters are always in the root Folder.
|
||||||
# If path is nil the first DC is returned otherwise match by name
|
# If path is nil the first DC is returned otherwise match by name
|
||||||
content.rootFolder.childEntity.each do |child|
|
content.rootFolder.childEntity.each do |child|
|
||||||
if child.is_a?(MockDatacenter)
|
if child.is_a?(RbVmomi::VIM::Datacenter)
|
||||||
return child if path.nil? || child.name == path
|
return child if path.nil? || child.name == path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -143,9 +159,12 @@ MockVirtualDiskManager = Object
|
||||||
MockVirtualMachine = Struct.new(
|
MockVirtualMachine = Struct.new(
|
||||||
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.VirtualMachine.html
|
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.VirtualMachine.html
|
||||||
# From VirtualMachine
|
# From VirtualMachine
|
||||||
:config, :snapshot, :summary,
|
:config, :runtime, :snapshot, :summary,
|
||||||
# From ManagedEntity
|
# From ManagedEntity
|
||||||
:name
|
:name,
|
||||||
|
# From RbVmomi::VIM::ManagedEntity
|
||||||
|
# https://github.com/vmware/rbvmomi/blob/master/lib/rbvmomi/vim/ManagedEntity.rb
|
||||||
|
:path
|
||||||
)
|
)
|
||||||
|
|
||||||
MockVirtualMachineSnapshot = Struct.new(
|
MockVirtualMachineSnapshot = Struct.new(
|
||||||
|
|
@ -256,6 +275,12 @@ MockVirtualMachineFileLayoutExFileInfo = Struct.new(
|
||||||
:key, :name, :size, :type, :uniqueSize
|
:key, :name, :size, :type, :uniqueSize
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MockVirtualMachineGuestSummary = Struct.new(
|
||||||
|
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.vm.Summary.GuestSummary.html
|
||||||
|
# From VirtualMachineGuestSummary
|
||||||
|
:hostName
|
||||||
|
)
|
||||||
|
|
||||||
MockVirtualMachineRuntimeInfo = Struct.new(
|
MockVirtualMachineRuntimeInfo = Struct.new(
|
||||||
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.vm.RuntimeInfo.html
|
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.vm.RuntimeInfo.html
|
||||||
# From VirtualMachineRuntimeInfo
|
# From VirtualMachineRuntimeInfo
|
||||||
|
|
@ -399,6 +424,10 @@ def mock_RbVmomi_VIM_Datacenter(options = {})
|
||||||
mock.datastore << mock_ds
|
mock.datastore << mock_ds
|
||||||
end
|
end
|
||||||
|
|
||||||
|
allow(mock).to receive(:is_a?) do |expected_type|
|
||||||
|
expected_type == RbVmomi::VIM::Datacenter
|
||||||
|
end
|
||||||
|
|
||||||
mock
|
mock
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -626,14 +655,21 @@ end
|
||||||
def mock_RbVmomi_VIM_VirtualMachine(options = {})
|
def mock_RbVmomi_VIM_VirtualMachine(options = {})
|
||||||
options[:snapshot_tree] = nil if options[:snapshot_tree].nil?
|
options[:snapshot_tree] = nil if options[:snapshot_tree].nil?
|
||||||
options[:name] = 'VM' + rand(65536).to_s if options[:name].nil?
|
options[:name] = 'VM' + rand(65536).to_s if options[:name].nil?
|
||||||
|
options[:path] = [] if options[:path].nil?
|
||||||
|
|
||||||
mock = MockVirtualMachine.new()
|
mock = MockVirtualMachine.new()
|
||||||
mock.config = MockVirtualMachineConfigInfo.new()
|
mock.config = MockVirtualMachineConfigInfo.new()
|
||||||
mock.config.hardware = MockVirtualHardware.new([])
|
mock.config.hardware = MockVirtualHardware.new([])
|
||||||
mock.summary = MockVirtualMachineSummary.new()
|
mock.summary = MockVirtualMachineSummary.new()
|
||||||
mock.summary.runtime = MockVirtualMachineRuntimeInfo.new()
|
mock.summary.runtime = MockVirtualMachineRuntimeInfo.new()
|
||||||
|
mock.summary.guest = MockVirtualMachineGuestSummary.new()
|
||||||
|
mock.runtime = mock.summary.runtime
|
||||||
|
|
||||||
mock.name = options[:name]
|
mock.name = options[:name]
|
||||||
|
mock.summary.guest.hostName = options[:hostname]
|
||||||
|
mock.runtime.bootTime = options[:boottime]
|
||||||
|
mock.runtime.powerState = options[:powerstate]
|
||||||
|
|
||||||
unless options[:snapshot_tree].nil?
|
unless options[:snapshot_tree].nil?
|
||||||
mock.snapshot = MockVirtualMachineSnapshotInfo.new()
|
mock.snapshot = MockVirtualMachineSnapshotInfo.new()
|
||||||
mock.snapshot.rootSnapshotList = []
|
mock.snapshot.rootSnapshotList = []
|
||||||
|
|
@ -642,6 +678,24 @@ def mock_RbVmomi_VIM_VirtualMachine(options = {})
|
||||||
# Create a recursive snapshot tree
|
# Create a recursive snapshot tree
|
||||||
recurse_snapshot_tree(options[:snapshot_tree],mock.snapshot.rootSnapshotList,index)
|
recurse_snapshot_tree(options[:snapshot_tree],mock.snapshot.rootSnapshotList,index)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Create an array of items that describe the path of the VM from the root folder
|
||||||
|
# all the way to the VM itself
|
||||||
|
mock.path = []
|
||||||
|
options[:path].each do |path_item|
|
||||||
|
mock_item = nil
|
||||||
|
case path_item[:type]
|
||||||
|
when 'folder'
|
||||||
|
mock_item = mock_RbVmomi_VIM_Folder({ :name => path_item[:name] })
|
||||||
|
when 'datacenter'
|
||||||
|
mock_item = mock_RbVmomi_VIM_Datacenter({ :name => path_item[:name] })
|
||||||
|
else
|
||||||
|
raise("Unknown mock type #{path_item[:type]} for mock_RbVmomi_VIM_VirtualMachine")
|
||||||
|
end
|
||||||
|
mock.path << [mock_item,path_item[:name]]
|
||||||
|
end
|
||||||
|
mock.path << [mock,options[:name]]
|
||||||
|
|
||||||
allow(mock).to receive(:is_a?) do |expected_type|
|
allow(mock).to receive(:is_a?) do |expected_type|
|
||||||
expected_type == RbVmomi::VIM::VirtualMachine
|
expected_type == RbVmomi::VIM::VirtualMachine
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,12 @@ require 'spec_helper'
|
||||||
# to enforce that certain methods are defined in the base classes
|
# to enforce that certain methods are defined in the base classes
|
||||||
|
|
||||||
describe 'Vmpooler::PoolManager::Provider::Base' do
|
describe 'Vmpooler::PoolManager::Provider::Base' do
|
||||||
|
let(:logger) { MockLogger.new }
|
||||||
|
let(:metrics) { Vmpooler::DummyStatsd.new }
|
||||||
let(:config) { {} }
|
let(:config) { {} }
|
||||||
|
let(:provider_name) { 'base' }
|
||||||
|
let(:provider_options) { { 'param' => 'value' } }
|
||||||
|
|
||||||
let(:fake_vm) {
|
let(:fake_vm) {
|
||||||
fake_vm = {}
|
fake_vm = {}
|
||||||
fake_vm['name'] = 'vm1'
|
fake_vm['name'] = 'vm1'
|
||||||
|
|
@ -16,11 +21,102 @@ describe 'Vmpooler::PoolManager::Provider::Base' do
|
||||||
fake_vm
|
fake_vm
|
||||||
}
|
}
|
||||||
|
|
||||||
subject { Vmpooler::PoolManager::Provider::Base.new(config) }
|
subject { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, provider_name, provider_options) }
|
||||||
|
|
||||||
|
# Helper attr_reader methods
|
||||||
|
describe '#logger' do
|
||||||
|
it 'should come from the provider initialization' do
|
||||||
|
expect(subject.logger).to be(logger)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#metrics' do
|
||||||
|
it 'should come from the provider initialization' do
|
||||||
|
expect(subject.metrics).to be(metrics)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#provider_options' do
|
||||||
|
it 'should come from the provider initialization' do
|
||||||
|
expect(subject.provider_options).to be(provider_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#pool_config' do
|
||||||
|
let(:poolname) { 'pool1' }
|
||||||
|
let(:config) { YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:pools:
|
||||||
|
- name: '#{poolname}'
|
||||||
|
alias: [ 'mockpool' ]
|
||||||
|
template: 'Templates/pool1'
|
||||||
|
folder: 'Pooler/pool1'
|
||||||
|
datastore: 'datastore0'
|
||||||
|
size: 5
|
||||||
|
timeout: 10
|
||||||
|
ready_ttl: 1440
|
||||||
|
clone_target: 'cluster1'
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
context 'Given a pool that does not exist' do
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.pool_config('missing_pool')).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Given a pool that does exist' do
|
||||||
|
it 'should return the pool\'s configuration' do
|
||||||
|
result = subject.pool_config(poolname)
|
||||||
|
expect(result['name']).to eq(poolname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#provider_config' do
|
||||||
|
let(:poolname) { 'pool1' }
|
||||||
|
let(:config) { YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:providers:
|
||||||
|
:#{provider_name}:
|
||||||
|
option1: 'value1'
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
context 'Given a misconfigured provider name' do
|
||||||
|
let(:config) { YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:providers:
|
||||||
|
:bad_provider:
|
||||||
|
option1: 'value1'
|
||||||
|
option2: 'value1'
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.provider_config).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Given a correct provider name' do
|
||||||
|
it 'should return the provider\'s configuration' do
|
||||||
|
result = subject.provider_config
|
||||||
|
expect(result['option1']).to eq('value1')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#global_config' do
|
||||||
|
it 'should come from the provider initialization' do
|
||||||
|
expect(subject.global_config).to be(config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pool Manager Methods
|
||||||
describe '#name' do
|
describe '#name' do
|
||||||
it 'should be base' do
|
it "should come from the provider initialization" do
|
||||||
expect(subject.name).to eq('base')
|
expect(subject.name).to eq(provider_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -32,25 +128,25 @@ describe 'Vmpooler::PoolManager::Provider::Base' do
|
||||||
|
|
||||||
describe '#get_vm_host' do
|
describe '#get_vm_host' do
|
||||||
it 'should raise error' do
|
it 'should raise error' do
|
||||||
expect{subject.get_vm_host('vm')}.to raise_error(/does not implement get_vm_host/)
|
expect{subject.get_vm_host('pool', 'vm')}.to raise_error(/does not implement get_vm_host/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#find_least_used_compatible_host' do
|
describe '#find_least_used_compatible_host' do
|
||||||
it 'should raise error' do
|
it 'should raise error' do
|
||||||
expect{subject.find_least_used_compatible_host('vm')}.to raise_error(/does not implement find_least_used_compatible_host/)
|
expect{subject.find_least_used_compatible_host('pool', 'vm')}.to raise_error(/does not implement find_least_used_compatible_host/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#migrate_vm_to_host' do
|
describe '#migrate_vm_to_host' do
|
||||||
it 'should raise error' do
|
it 'should raise error' do
|
||||||
expect{subject.migrate_vm_to_host('vm','host')}.to raise_error(/does not implement migrate_vm_to_host/)
|
expect{subject.migrate_vm_to_host('pool', 'vm','host')}.to raise_error(/does not implement migrate_vm_to_host/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#get_vm' do
|
describe '#get_vm' do
|
||||||
it 'should raise error' do
|
it 'should raise error' do
|
||||||
expect{subject.get_vm('vm')}.to raise_error(/does not implement get_vm/)
|
expect{subject.get_vm('pool', 'vm')}.to raise_error(/does not implement get_vm/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -60,33 +156,51 @@ describe 'Vmpooler::PoolManager::Provider::Base' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#create_disk' do
|
||||||
|
it 'should raise error' do
|
||||||
|
expect{subject.create_disk('pool', 'vm', 10)}.to raise_error(/does not implement create_disk/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#create_snapshot' do
|
||||||
|
it 'should raise error' do
|
||||||
|
expect{subject.create_snapshot('pool', 'vm', 'snapshot')}.to raise_error(/does not implement create_snapshot/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#revert_snapshot' do
|
||||||
|
it 'should raise error' do
|
||||||
|
expect{subject.revert_snapshot('pool', 'vm', 'snapshot')}.to raise_error(/does not implement revert_snapshot/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#destroy_vm' do
|
describe '#destroy_vm' do
|
||||||
it 'should raise error' do
|
it 'should raise error' do
|
||||||
expect{subject.destroy_vm('vm','pool')}.to raise_error(/does not implement destroy_vm/)
|
expect{subject.destroy_vm('pool', 'vm')}.to raise_error(/does not implement destroy_vm/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#vm_ready?' do
|
describe '#vm_ready?' do
|
||||||
it 'should raise error' do
|
it 'should raise error' do
|
||||||
expect{subject.vm_ready?('vm','pool','timeout')}.to raise_error(/does not implement vm_ready?/)
|
expect{subject.vm_ready?('pool', 'vm')}.to raise_error(/does not implement vm_ready?/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#vm_exists?' do
|
describe '#vm_exists?' do
|
||||||
it 'should raise error' do
|
it 'should raise error' do
|
||||||
expect{subject.vm_exists?('vm')}.to raise_error(/does not implement/)
|
expect{subject.vm_exists?('pool', 'vm')}.to raise_error(/does not implement/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should return true when get_vm returns an object' do
|
it 'should return true when get_vm returns an object' do
|
||||||
allow(subject).to receive(:get_vm).with('vm').and_return(fake_vm)
|
allow(subject).to receive(:get_vm).with('pool', 'vm').and_return(fake_vm)
|
||||||
|
|
||||||
expect(subject.vm_exists?('vm')).to eq(true)
|
expect(subject.vm_exists?('pool', 'vm')).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should return false when get_vm returns nil' do
|
it 'should return false when get_vm returns nil' do
|
||||||
allow(subject).to receive(:get_vm).with('vm').and_return(nil)
|
allow(subject).to receive(:get_vm).with('pool', 'vm').and_return(nil)
|
||||||
|
|
||||||
expect(subject.vm_exists?('vm')).to eq(false)
|
expect(subject.vm_exists?('pool', 'vm')).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue