1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
|
require 'yaml'
# A class to manage VM leases
#
# This class uses YAML encoded VM annotations (config.annotation) to manage a
# lease system. It helps add such lease info onto new and existing VMs and to
# find VMs that have expired leases or that are about to have expired leases.
# The calling code can use those to generate emails with about-to-expire
# notifications, suspend, power off or destroy VMs that have exceeded their
# lease, etc.
class LeaseTool
# Lists of VM properties the LeaseTool needs to do its job. Can be used to
# construct larger property collector calls that retrieve more info than just
# one subsystem needs.
# @return [Array] List of property names
def vms_props_list
['name', 'config.annotation']
end
# Fetch all VM properties that the LeaseTool needs on all VMs passed in.
# @param vms [Array] List of VIM::VirtualMachine instances
# @return [Hash] Hash of VMs as keys and their properties as values
def get_vms_props vms
out = {}
if vms.length > 0
pc = vms.first._connection.serviceContent.propertyCollector
out = pc.collectMultiple(vms, 'name', 'config.annotation')
end
out
end
# Retrieve the current time as used by the lease tool.
# @return [Time] Current time as used by the lease tool
def current_time
# XXX: Should swith to time provided by VC
Time.now
end
# Helper function that sets the lease info in a passed in VM config. If there
# is no annotation, it is added. If there is an annotation, it is updated to
# include the lease info. Note that if the annotation isn't YAML, it is
# overwritten.
# @param vmconfig [Hash] Virtual Machine config spec
# @param lease_minutes [int] Time to lease expiration from now in minutes
# @return [Hash] Updated Virtual Machine config spec
def set_lease_in_vm_config vmconfig, lease_minutes
annotation = vmconfig[:annotation]
annotation ||= ""
note = YAML.load annotation
if !note.is_a?(Hash)
note = {}
end
lease = current_time + lease_minutes * 60
note['lease'] = lease
vmconfig[:annotation] = YAML.dump(note)
vmconfig
end
# Issue ReconfigVM_Task on the VM to update the lease. User can pass in current
# annotation, but if not, it is retrieved on demand. A task is returned, i.e.
# function doesn't wait for completion.
# @param vm [VIM::VirtualMachine] Virtual Machine instance
# @param lease_minutes [int] Time to lease expiration from now in minutes
# @param annotation [String] 'config.annotation' property of the VM. Optional.
# @return [VIM::Task] VM reconfiguration task
def set_lease_on_vm_task vm, lease_minutes, annotation = nil
if !annotation
annotation = vm.collect 'config.annotation'
end
vmconfig = {:annotation => annotation}
vmconfig = set_lease_in_vm_config vmconfig, lease_minutes
# XXX: It may be a good idea to cite the VM version here to avoid
# concurrent writes to the annotation stepping on each others toes
vm.ReconfigVM_Task(:spec => vmconfig)
end
# Issue ReconfigVM_Task to set the lease on all VMs that currently do not
# have a lease. All VM reconfigurations are done in parallel and the function
# waits for all of them to complete
# @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
# @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
# @option opts [int] :lease_minutes Time to lease expiration from now in minutes
# @return [Array] List of previously leaseless VMs that now have a lease
def set_lease_on_leaseless_vms vms, vmprops, opts = {}
lease_minutes = opts[:lease_minutes]
if !lease_minutes
raise "Expected lease_minutes to be specified"
end
vms = find_leaseless_vms vms, vmprops
if vms.length > 0
tasks = vms.map do |vm|
annotation = vmprops[vm]['config.annotation']
task = set_lease_on_vm_task(vm, lease_minutes, annotation)
task
end
si = vms.first._connection.serviceInstance
si.wait_for_multiple_tasks [], tasks
end
vms
end
# Filter the list of passed in Virtual Machines and find the ones that currently
# do not have a lease.
# @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
# @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
# @return [Array] List of leaseless VMs
def find_leaseless_vms vms, vmprops
vms.reject do |vm|
props = vmprops[vm]
annotation = props['config.annotation']
if annotation
note = YAML.load annotation
note.is_a?(Hash) && note['lease']
end
end
end
# Filter the list of passed in Virtul Machines and find the one that are
# expired. A time offset can be used to identify VMs that will expire at
# a certain point in the future.
# If a VM doesn't have a lease, it is treated as never expiring.
# @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
# @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
# @option opts [int] :time_delta Time delta (seconds) to be added to current time
# @return [Array] List of expired VMs
def filter_expired_vms vms, vmprops, opts = {}
time_delta = opts[:time_delta] || 0
time = current_time + time_delta
out = vms.map do |vm|
props = vmprops[vm]
next unless annotation = props['config.annotation']
note = YAML.load annotation
next unless note.is_a?(Hash) && lease = note['lease']
next unless time > lease
time_to_expiration = ((lease - time) + time_delta)
[vm, time_to_expiration]
end.compact
out = Hash[out]
out
end
end
|