mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 10:08:40 -05:00
(POOLER-107) Add configuration API endpoint
This commit adds a configuration endpoint to the vmpooler API. Pool size, and pool template, can be adjusted for pools that are configured at vmpooler application start time. Pool template changes trigger a pool refresh, and the new template has delta disks created automatically by vmpooler. Additionally, the capability to create template delta disks is added to the vsphere provider, and this is implemented to ensure that templates have delta disks created at application start time. The mechanism used to find template VM objects is simplified to make the flow of logic easier to understand. As an additional benefit, performance of this lookup is improved by using FindByInventoryPath.
This commit is contained in:
parent
00970ffc9e
commit
9758adccfe
9 changed files with 803 additions and 44 deletions
56
docs/API.md
56
docs/API.md
|
|
@ -540,3 +540,59 @@ $ curl -G -d 'from=2015-03-10' -d 'to=2015-03-11' --url vmpooler.company.com/api
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Changing configuration via API
|
||||||
|
|
||||||
|
##### POST /config/poolsize
|
||||||
|
|
||||||
|
Change pool size without having to restart the service.
|
||||||
|
|
||||||
|
All pool template changes requested must be for pools that exist in the vmpooler configuration running, or a 404 code will be returned
|
||||||
|
|
||||||
|
When a pool size is changed due to the configuration posted a 201 status will be returned. When the pool configuration is valid, but will not result in any changes, 200 is returned.
|
||||||
|
|
||||||
|
Pool size configuration changes persist through application restarts, and take precedence over a pool size value configured in the pool configuration provided when the application starts. This persistence is dependent on redis. So, if the redis data is lost then the configuration updates revert to those provided at startup at the next application start.
|
||||||
|
|
||||||
|
An authentication token is required in order to change pool configuration when authentication is configured.
|
||||||
|
Responses:
|
||||||
|
* 200 - No changes required
|
||||||
|
* 201 - Changes made on at least one pool with changes requested
|
||||||
|
* 404 - An error was encountered while evaluating requested changes
|
||||||
|
```
|
||||||
|
$ curl -X POST -H "Content-Type: application/json" -d '{"debian-7-i386":"2","debian-7-x86_64":"1"}' --url https://vmpooler.company.com/api/v1/config/poolsize
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### POST /config/pooltemplate
|
||||||
|
|
||||||
|
Change the template configured for a pool, and replenish the pool with instances built from the new template.
|
||||||
|
|
||||||
|
All pool template changes requested must be for pools that exist in the vmpooler configuration running, or a 404 code will be returned
|
||||||
|
|
||||||
|
When a pool template is changed due to the configuration posted a 201 status will be returned. When the pool configuration is valid, but will not result in any changes, 200 is returned.
|
||||||
|
|
||||||
|
A pool template being updated will cause the following actions, which are logged in vmpooler.log:
|
||||||
|
* Destroy all instances for the pool template being updated that are in the ready and pending state
|
||||||
|
* Halt repopulating the pool while creating template deltas for the newly configured template
|
||||||
|
* Unblock pool population and let the pool replenish with instances based on the newly configured template
|
||||||
|
|
||||||
|
Pool template changes persist through application restarts, and take precedence over a pool template configured in the pool configuration provided when the application starts. This persistence is dependent on redis. As a result, if the redis data is lost then the configuration values revert to those provided at startup at the next application start.
|
||||||
|
|
||||||
|
An authentication token is required in order to change pool configuration when authentication is configured.
|
||||||
|
|
||||||
|
Responses:
|
||||||
|
* 200 - No changes required
|
||||||
|
* 201 - Changes made on at least one pool with changes requested
|
||||||
|
* 404 - An error was encountered while evaluating requested changes
|
||||||
|
```
|
||||||
|
$ curl -X POST -H "Content-Type: application/json" -d '{"debian-7-i386":"templates/debian-7-i386"}' --url https://vmpooler.company.com/api/v1/config/pooltemplate
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,16 @@ module Vmpooler
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pool_index(pools)
|
||||||
|
pools_hash = {}
|
||||||
|
index = 0
|
||||||
|
for pool in pools
|
||||||
|
pools_hash[pool['name']] = index
|
||||||
|
index += 1
|
||||||
|
end
|
||||||
|
pools_hash
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,44 @@ module Vmpooler
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_pool_size(payload)
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
pool_index = pool_index(pools)
|
||||||
|
pools_updated = 0
|
||||||
|
|
||||||
|
payload.each do |poolname, size|
|
||||||
|
unless pools[pool_index[poolname]]['size'] == size.to_i
|
||||||
|
pools[pool_index[poolname]]['size'] = size.to_i
|
||||||
|
backend.hset('vmpooler__config__poolsize', poolname, size)
|
||||||
|
pools_updated += 1
|
||||||
|
status 201
|
||||||
|
end
|
||||||
|
end
|
||||||
|
status 200 unless pools_updated > 0
|
||||||
|
result['ok'] = true
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_pool_template(payload)
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
pool_index = pool_index(pools)
|
||||||
|
pools_updated = 0
|
||||||
|
|
||||||
|
payload.each do |poolname, template|
|
||||||
|
unless pools[pool_index[poolname]]['template'] == template
|
||||||
|
pools[pool_index[poolname]]['template'] = template
|
||||||
|
backend.hset('vmpooler__config__template', poolname, template)
|
||||||
|
pools_updated += 1
|
||||||
|
status 201
|
||||||
|
end
|
||||||
|
end
|
||||||
|
status 200 unless pools_updated > 0
|
||||||
|
result['ok'] = true
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
# Provide run-time statistics
|
# Provide run-time statistics
|
||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
|
|
@ -502,6 +540,24 @@ module Vmpooler
|
||||||
invalid
|
invalid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def invalid_template_or_size(payload)
|
||||||
|
invalid = []
|
||||||
|
payload.each do |pool, size|
|
||||||
|
invalid << pool unless pool_exists?(pool)
|
||||||
|
Integer(size) rescue invalid << pool
|
||||||
|
end
|
||||||
|
invalid
|
||||||
|
end
|
||||||
|
|
||||||
|
def invalid_template_or_path(payload)
|
||||||
|
invalid = []
|
||||||
|
payload.each do |pool, template|
|
||||||
|
invalid << pool unless pool_exists?(pool)
|
||||||
|
invalid << pool unless template.include? '/'
|
||||||
|
end
|
||||||
|
invalid
|
||||||
|
end
|
||||||
|
|
||||||
post "#{api_prefix}/vm/:template/?" do
|
post "#{api_prefix}/vm/:template/?" do
|
||||||
content_type :json
|
content_type :json
|
||||||
result = { 'ok' => false }
|
result = { 'ok' => false }
|
||||||
|
|
@ -747,6 +803,58 @@ module Vmpooler
|
||||||
|
|
||||||
JSON.pretty_generate(result)
|
JSON.pretty_generate(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post "#{api_prefix}/config/poolsize/?" do
|
||||||
|
content_type :json
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
need_token! if Vmpooler::API.settings.config[:auth]
|
||||||
|
|
||||||
|
payload = JSON.parse(request.body.read)
|
||||||
|
|
||||||
|
if payload
|
||||||
|
invalid = invalid_template_or_size(payload)
|
||||||
|
if invalid.empty?
|
||||||
|
result = update_pool_size(payload)
|
||||||
|
else
|
||||||
|
invalid.each do |bad_template|
|
||||||
|
metrics.increment("config.invalid.#{bad_template}")
|
||||||
|
end
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
else
|
||||||
|
metrics.increment('config.invalid.unknown')
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
|
||||||
|
JSON.pretty_generate(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
post "#{api_prefix}/config/pooltemplate/?" do
|
||||||
|
content_type :json
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
need_token! if Vmpooler::API.settings.config[:auth]
|
||||||
|
|
||||||
|
payload = JSON.parse(request.body.read)
|
||||||
|
|
||||||
|
if payload
|
||||||
|
invalid = invalid_template_or_path(payload)
|
||||||
|
if invalid.empty?
|
||||||
|
result = update_pool_template(payload)
|
||||||
|
else
|
||||||
|
invalid.each do |bad_template|
|
||||||
|
metrics.increment("config.invalid.#{bad_template}")
|
||||||
|
end
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
else
|
||||||
|
metrics.increment('config.invalid.unknown')
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
|
||||||
|
JSON.pretty_generate(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -187,9 +187,9 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def move_vm_queue(pool, vm, queue_from, queue_to, msg)
|
def move_vm_queue(pool, vm, queue_from, queue_to, msg = nil)
|
||||||
$redis.smove("vmpooler__#{queue_from}__#{pool}", "vmpooler__#{queue_to}__#{pool}", vm)
|
$redis.smove("vmpooler__#{queue_from}__#{pool}", "vmpooler__#{queue_to}__#{pool}", vm)
|
||||||
$logger.log('d', "[!] [#{pool}] '#{vm}' #{msg}")
|
$logger.log('d', "[!] [#{pool}] '#{vm}' #{msg}") if msg
|
||||||
end
|
end
|
||||||
|
|
||||||
# Clone a VM
|
# Clone a VM
|
||||||
|
|
@ -555,6 +555,75 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepare_template(pool, provider)
|
||||||
|
if $config[:config]['create_template_delta_disks']
|
||||||
|
# Ensure templates are evaluated for delta disk creation on startup
|
||||||
|
return if $redis.hget('vmpooler__config__updating', pool['name'])
|
||||||
|
unless $redis.hget('vmpooler__template__prepared', pool['name'])
|
||||||
|
begin
|
||||||
|
$redis.hset('vmpooler__config__updating', pool['name'], 1)
|
||||||
|
provider.create_template_delta_disks(pool)
|
||||||
|
$redis.hset('vmpooler__template__prepared', pool['name'], pool['template'])
|
||||||
|
ensure
|
||||||
|
$redis.hdel('vmpooler__config__updating', pool['name'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_pool_template(pool, provider)
|
||||||
|
$redis.hset('vmpooler__template', pool['name'], pool['template']) unless $redis.hget('vmpooler__template', pool['name'])
|
||||||
|
return unless $redis.hget('vmpooler__config__template', pool['name'])
|
||||||
|
unless $redis.hget('vmpooler__config__template', pool['name']) == $redis.hget('vmpooler__template', pool['name'])
|
||||||
|
# Ensure we are only updating a template once
|
||||||
|
return if $redis.hget('vmpooler__config__updating', pool['name'])
|
||||||
|
begin
|
||||||
|
$redis.hset('vmpooler__config__updating', pool['name'], 1)
|
||||||
|
|
||||||
|
old_template_name = $redis.hget('vmpooler__template', pool['name'])
|
||||||
|
new_template_name = $redis.hget('vmpooler__config__template', pool['name'])
|
||||||
|
pool['template'] = new_template_name
|
||||||
|
$redis.hset('vmpooler__template', pool['name'], new_template_name)
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] template updated from #{old_template_name} to #{new_template_name}")
|
||||||
|
# Remove all ready and pending VMs so new instances are created from the new template
|
||||||
|
if $redis.scard("vmpooler__ready__#{pool['name']}") > 0
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] removing ready and pending instances")
|
||||||
|
$redis.smembers("vmpooler__ready__#{pool['name']}").each do |vm|
|
||||||
|
$redis.smove("vmpooler__ready__#{pool['name']}", "vmpooler__completed__#{pool['name']}", vm)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if $redis.scard("vmpooler__pending__#{pool['name']}") > 0
|
||||||
|
$redis.smembers("vmpooler__pending__#{pool['name']}").each do |vm|
|
||||||
|
$redis.smove("vmpooler__pending__#{pool['name']}", "vmpooler__completed__#{pool['name']}", vm)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# Prepare template for deployment
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] creating template deltas")
|
||||||
|
provider.create_template_delta_disks(pool)
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] template deltas have been created")
|
||||||
|
ensure
|
||||||
|
$redis.hdel('vmpooler__config__updating', pool['name'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_excess_vms(pool, provider, ready, total)
|
||||||
|
unless ready.nil?
|
||||||
|
if total > pool['size']
|
||||||
|
difference = ready - pool['size']
|
||||||
|
difference.times do
|
||||||
|
next_vm = $redis.spop("vmpooler__ready__#{pool['name']}")
|
||||||
|
move_vm_queue(pool['name'], next_vm, 'ready', 'completed')
|
||||||
|
end
|
||||||
|
if total > ready
|
||||||
|
$redis.smembers("vmpooler__pending__#{pool['name']}").each do |vm|
|
||||||
|
move_vm_queue(pool['name'], vm, 'pending', 'completed')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def _check_pool(pool, provider)
|
def _check_pool(pool, provider)
|
||||||
pool_check_response = {
|
pool_check_response = {
|
||||||
discovered_vms: 0,
|
discovered_vms: 0,
|
||||||
|
|
@ -683,36 +752,59 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Create template delta disks
|
||||||
|
prepare_template(pool, provider)
|
||||||
|
|
||||||
|
# UPDATE TEMPLATE
|
||||||
|
# Check to see if a pool template change has been made via the configuration API
|
||||||
|
# Since check_pool runs in a loop it does not otherwise identify this change when running
|
||||||
|
update_pool_template(pool, provider)
|
||||||
|
|
||||||
# REPOPULATE
|
# REPOPULATE
|
||||||
ready = $redis.scard("vmpooler__ready__#{pool['name']}")
|
# Do not attempt to repopulate a pool while a template is updating
|
||||||
total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
|
unless $redis.hget('vmpooler__config__updating', pool['name'])
|
||||||
|
ready = $redis.scard("vmpooler__ready__#{pool['name']}")
|
||||||
|
total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
|
||||||
|
|
||||||
$metrics.gauge("ready.#{pool['name']}", $redis.scard("vmpooler__ready__#{pool['name']}"))
|
$metrics.gauge("ready.#{pool['name']}", $redis.scard("vmpooler__ready__#{pool['name']}"))
|
||||||
$metrics.gauge("running.#{pool['name']}", $redis.scard("vmpooler__running__#{pool['name']}"))
|
$metrics.gauge("running.#{pool['name']}", $redis.scard("vmpooler__running__#{pool['name']}"))
|
||||||
|
|
||||||
if $redis.get("vmpooler__empty__#{pool['name']}")
|
if $redis.get("vmpooler__empty__#{pool['name']}")
|
||||||
$redis.del("vmpooler__empty__#{pool['name']}") unless ready.zero?
|
$redis.del("vmpooler__empty__#{pool['name']}") unless ready.zero?
|
||||||
elsif ready.zero?
|
elsif ready.zero?
|
||||||
$redis.set("vmpooler__empty__#{pool['name']}", 'true')
|
$redis.set("vmpooler__empty__#{pool['name']}", 'true')
|
||||||
$logger.log('s', "[!] [#{pool['name']}] is empty")
|
$logger.log('s', "[!] [#{pool['name']}] is empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
if total < pool['size']
|
# Check to see if a pool size change has been made via the configuration API
|
||||||
(1..(pool['size'] - total)).each do |_i|
|
# Since check_pool runs in a loop it does not
|
||||||
if $redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
|
# otherwise identify this change when running
|
||||||
begin
|
if $redis.hget('vmpooler__config__poolsize', pool['name'])
|
||||||
$redis.incr('vmpooler__tasks__clone')
|
unless $redis.hget('vmpooler__config__poolsize', pool['name']).to_i == pool['size']
|
||||||
pool_check_response[:cloned_vms] += 1
|
pool['size'] = Integer($redis.hget('vmpooler__config__poolsize', pool['name']))
|
||||||
clone_vm(pool, provider)
|
end
|
||||||
rescue => err
|
end
|
||||||
$logger.log('s', "[!] [#{pool['name']}] clone failed during check_pool with an error: #{err}")
|
|
||||||
$redis.decr('vmpooler__tasks__clone')
|
if total < pool['size']
|
||||||
raise
|
(1..(pool['size'] - total)).each do |_i|
|
||||||
|
if $redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
|
||||||
|
begin
|
||||||
|
$redis.incr('vmpooler__tasks__clone')
|
||||||
|
pool_check_response[:cloned_vms] += 1
|
||||||
|
clone_vm(pool, provider)
|
||||||
|
rescue => err
|
||||||
|
$logger.log('s', "[!] [#{pool['name']}] clone failed during check_pool with an error: #{err}")
|
||||||
|
$redis.decr('vmpooler__tasks__clone')
|
||||||
|
raise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Remove VMs in excess of the configured pool size
|
||||||
|
remove_excess_vms(pool, provider, ready, total)
|
||||||
|
|
||||||
pool_check_response
|
pool_check_response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -739,6 +831,10 @@ module Vmpooler
|
||||||
$redis.set('vmpooler__tasks__clone', 0)
|
$redis.set('vmpooler__tasks__clone', 0)
|
||||||
# Clear out vmpooler__migrations since stale entries may be left after a restart
|
# Clear out vmpooler__migrations since stale entries may be left after a restart
|
||||||
$redis.del('vmpooler__migration')
|
$redis.del('vmpooler__migration')
|
||||||
|
# Clear out any configuration changes in flight that were interrupted
|
||||||
|
$redis.del('vmpooler__config__updating')
|
||||||
|
# Ensure template deltas are created on each startup
|
||||||
|
$redis.del('vmpooler__template__prepared')
|
||||||
|
|
||||||
# Copy vSphere settings to correct location. This happens with older configuration files
|
# Copy vSphere settings to correct location. This happens with older configuration files
|
||||||
if !$config[:vsphere].nil? && ($config[:providers].nil? || $config[:providers][:vsphere].nil?)
|
if !$config[:vsphere].nil? && ($config[:providers].nil? || $config[:providers][:vsphere].nil?)
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,14 @@ module Vmpooler
|
||||||
def vm_exists?(pool_name, vm_name)
|
def vm_exists?(pool_name, vm_name)
|
||||||
!get_vm(pool_name, vm_name).nil?
|
!get_vm(pool_name, vm_name).nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# inputs
|
||||||
|
# [Hash] pool : Configuration for the pool
|
||||||
|
# returns
|
||||||
|
# nil when successful. Raises error when encountered
|
||||||
|
def create_template_delta_disks(pool)
|
||||||
|
raise("#{self.class.name} does not implement create_template_delta_disks")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -197,17 +197,10 @@ module Vmpooler
|
||||||
target_cluster_name = get_target_cluster_from_config(pool_name)
|
target_cluster_name = get_target_cluster_from_config(pool_name)
|
||||||
target_datacenter_name = get_target_datacenter_from_config(pool_name)
|
target_datacenter_name = get_target_datacenter_from_config(pool_name)
|
||||||
|
|
||||||
# Extract the template VM name from the full path
|
# Get the template VM object
|
||||||
raise("Pool #{pool_name} did not specify a full path for the template for the provider #{name}") unless template_path =~ /\//
|
raise("Pool #{pool_name} did not 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_vm_object = find_template_vm(pool, connection)
|
||||||
template_folder_object = find_folder(templatefolders.join('/'), connection, target_datacenter_name)
|
|
||||||
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.
|
# Annotate with creation time, origin template, etc.
|
||||||
# Add extraconfig options that can be queried by vmtools
|
# Add extraconfig options that can be queried by vmtools
|
||||||
|
|
@ -964,6 +957,30 @@ module Vmpooler
|
||||||
raise("Cannot create folder #{new_folder}") if folder_object.nil?
|
raise("Cannot create folder #{new_folder}") if folder_object.nil?
|
||||||
folder_object
|
folder_object
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_template_vm(pool, connection)
|
||||||
|
datacenter = get_target_datacenter_from_config(pool['name'])
|
||||||
|
raise('cannot find datacenter') if datacenter.nil?
|
||||||
|
|
||||||
|
propSpecs = {
|
||||||
|
:entity => self,
|
||||||
|
:inventoryPath => "#{datacenter}/vm/#{pool['template']}"
|
||||||
|
}
|
||||||
|
|
||||||
|
template_vm_object = connection.searchIndex.FindByInventoryPath(propSpecs)
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
160
spec/integration/api/v1/config_spec.rb
Normal file
160
spec/integration/api/v1/config_spec.rb
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'rack/test'
|
||||||
|
|
||||||
|
module Vmpooler
|
||||||
|
class API
|
||||||
|
module Helpers
|
||||||
|
def authenticate(auth, username_str, password_str)
|
||||||
|
username_str == 'admin' and password_str == 's3cr3t'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe Vmpooler::API::V1 do
|
||||||
|
include Rack::Test::Methods
|
||||||
|
|
||||||
|
def app()
|
||||||
|
Vmpooler::API
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '/config/pooltemplate' do
|
||||||
|
let(:prefix) { '/api/v1' }
|
||||||
|
let(:metrics) { Vmpooler::DummyStatsd.new }
|
||||||
|
let(:config) {
|
||||||
|
{
|
||||||
|
config: {
|
||||||
|
'site_name' => 'test pooler',
|
||||||
|
'vm_lifetime_auth' => 2,
|
||||||
|
},
|
||||||
|
pools: [
|
||||||
|
{'name' => 'pool1', 'size' => 5, 'template' => 'templates/pool1'},
|
||||||
|
{'name' => 'pool2', 'size' => 10}
|
||||||
|
],
|
||||||
|
statsd: { 'prefix' => 'stats_prefix'},
|
||||||
|
alias: { 'poolone' => 'pool1' },
|
||||||
|
pool_names: [ 'pool1', 'pool2', 'poolone' ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let(:current_time) { Time.now }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
redis.flushdb
|
||||||
|
|
||||||
|
app.settings.set :config, config
|
||||||
|
app.settings.set :redis, redis
|
||||||
|
app.settings.set :metrics, metrics
|
||||||
|
app.settings.set :config, auth: false
|
||||||
|
create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /config/pooltemplate' do
|
||||||
|
it 'updates a pool template' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"pool1":"templates/new_template"}'
|
||||||
|
expect_json(ok = true, http = 201)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails on nonexistent pools' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"poolpoolpool":"templates/newtemplate"}'
|
||||||
|
expect_json(ok = false, http = 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates multiple pools' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"pool1":"templates/new_template","pool2":"templates/new_template2"}'
|
||||||
|
expect_json(ok = true, http = 201)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when not all pools exist' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"pool1":"templates/new_template","pool3":"templates/new_template2"}'
|
||||||
|
expect_json(ok = false, http = 404)
|
||||||
|
|
||||||
|
expected = { ok: false }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns no changes when the template does not change' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"pool1":"templates/pool1"}'
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when a invalid template parameter is provided' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"pool1":"template1"}'
|
||||||
|
expect_json(ok = false, http = 404)
|
||||||
|
|
||||||
|
expected = { ok: false }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /config/poolsize' do
|
||||||
|
it 'changes a pool size' do
|
||||||
|
post "#{prefix}/config/poolsize", '{"pool1":"2"}'
|
||||||
|
expect_json(ok = true, http = 201)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'changes a pool size for multiple pools' do
|
||||||
|
post "#{prefix}/config/poolsize", '{"pool1":"2","pool2":"2"}'
|
||||||
|
expect_json(ok = true, http = 201)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when a specified pool does not exist' do
|
||||||
|
post "#{prefix}/config/poolsize", '{"pool10":"2"}'
|
||||||
|
expect_json(ok = false, http = 404)
|
||||||
|
|
||||||
|
expected = { ok: false }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'succeeds with 200 when no change is required' do
|
||||||
|
post "#{prefix}/config/poolsize", '{"pool1":"5"}'
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'succeeds with 201 when at least one pool changes' do
|
||||||
|
post "#{prefix}/config/poolsize", '{"pool1":"5","pool2":"5"}'
|
||||||
|
expect_json(ok = true, http = 201)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when a non-integer value is provided for size' do
|
||||||
|
post "#{prefix}/config/poolsize", '{"pool1":"four"}'
|
||||||
|
expect_json(ok = false, http = 404)
|
||||||
|
|
||||||
|
expected = { ok: false }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1503,6 +1503,220 @@ EOT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'update_pool_template' do
|
||||||
|
let(:template) { 'templates/pool_template' }
|
||||||
|
let(:new_template) { 'templates/new_pool_template' }
|
||||||
|
let(:vsphere_provider) { double('vsphere_provider') }
|
||||||
|
let(:config) {
|
||||||
|
YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:pools:
|
||||||
|
- name: #{pool}
|
||||||
|
template: "#{template}"
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
expect(subject).not_to be_nil
|
||||||
|
redis.del('vmpooler__template')
|
||||||
|
redis.del('vmpooler__config__template')
|
||||||
|
redis.del('vmpooler__config__updating')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns when vmpooler config template is not set' do
|
||||||
|
expect(subject.update_pool_template(config[:pools][0], provider)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with template that requires no change' do
|
||||||
|
before(:each) do
|
||||||
|
redis.hset('vmpooler__template', pool, template)
|
||||||
|
redis.hset('vmpooler__config__template', pool, template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return' do
|
||||||
|
expect(subject.update_pool_template(config[:pools][0], provider)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a pool that requires an update' do
|
||||||
|
before(:each) do
|
||||||
|
redis.hset('vmpooler__template', pool, template)
|
||||||
|
redis.hset('vmpooler__config__template', pool, new_template)
|
||||||
|
allow(logger).to receive(:log)
|
||||||
|
allow(redis).to receive(:hset)
|
||||||
|
expect(provider).to receive(:create_template_delta_disks).with(config[:pools][0])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should update the configuration value' do
|
||||||
|
expect(redis).to receive(:hset).with('vmpooler__template', pool, new_template)
|
||||||
|
|
||||||
|
subject.update_pool_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should log a message for updating the template' do
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] template updated from #{template} to #{new_template}")
|
||||||
|
|
||||||
|
subject.update_pool_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should log messages for creating template deltas' do
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] creating template deltas")
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] template deltas have been created")
|
||||||
|
|
||||||
|
subject.update_pool_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with ready and pending vms' do
|
||||||
|
let(:vmname) { 'vm2' }
|
||||||
|
before(:each) do
|
||||||
|
create_ready_vm(pool,vmname)
|
||||||
|
create_pending_vm(pool,vmname)
|
||||||
|
redis.hset('vmpooler__template', pool, template)
|
||||||
|
redis.hset('vmpooler__config__template', pool, new_template)
|
||||||
|
allow(logger).to receive(:log)
|
||||||
|
allow(redis).to receive(:smove)
|
||||||
|
expect(provider).to receive(:create_template_delta_disks).with(config[:pools][0])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should log a message for removing ready vms' do
|
||||||
|
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] removing ready and pending instances")
|
||||||
|
|
||||||
|
subject.update_pool_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
it 'should remove ready vms' do
|
||||||
|
expect(redis).to receive(:smove).with("vmpooler__ready__#{pool}", "vmpooler__completed__#{pool}", vmname)
|
||||||
|
|
||||||
|
subject.update_pool_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should remove pending vms' do
|
||||||
|
expect(redis).to receive(:smove).with("vmpooler__pending__#{pool}", "vmpooler__completed__#{pool}", vmname)
|
||||||
|
|
||||||
|
subject.update_pool_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when already updating' do
|
||||||
|
before(:each) do
|
||||||
|
redis.hset('vmpooler__template', pool, template)
|
||||||
|
redis.hset('vmpooler__config__template', pool, new_template)
|
||||||
|
redis.hset('vmpooler__config__updating', pool, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return' do
|
||||||
|
expect(subject.update_pool_template(config[:pools][0], provider)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'remove_excess_vms' do
|
||||||
|
let(:config) {
|
||||||
|
YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:pools:
|
||||||
|
- name: #{pool}
|
||||||
|
size: 2
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
expect(subject).not_to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a nil ready value' do
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.remove_excess_vms(config[:pools][0], provider, nil, nil)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a total size less than the pool size' do
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.remove_excess_vms(config[:pools][0], provider, 1, 2)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a total size greater than the pool size' do
|
||||||
|
it 'should remove excess ready vms' do
|
||||||
|
expect(subject).to receive(:move_vm_queue).exactly(2).times
|
||||||
|
|
||||||
|
subject.remove_excess_vms(config[:pools][0], provider, 4, 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should remove excess pending vms' do
|
||||||
|
create_pending_vm(pool,'vm1')
|
||||||
|
create_pending_vm(pool,'vm2')
|
||||||
|
create_pending_vm(pool,'vm3')
|
||||||
|
expect(subject).to receive(:move_vm_queue).exactly(3).times
|
||||||
|
|
||||||
|
subject.remove_excess_vms(config[:pools][0], provider, 2, 5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'prepare_template' do
|
||||||
|
let(:config) { YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:config:
|
||||||
|
create_template_delta_disks: true
|
||||||
|
:providers:
|
||||||
|
:mock:
|
||||||
|
:pools:
|
||||||
|
- name: '#{pool}'
|
||||||
|
size: 1
|
||||||
|
template: 'templates/pool1'
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it 'should return if a pool configuration is updating' do
|
||||||
|
redis.hset('vmpooler__config__updating', pool, 1)
|
||||||
|
|
||||||
|
expect(subject.prepare_template(config[:pools][0], provider)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return when a template is prepared' do
|
||||||
|
redis.hset('vmpooler__template__prepared', pool, pool['template'])
|
||||||
|
|
||||||
|
expect(subject.prepare_template(config[:pools][0], provider)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when creating the template delta disks' do
|
||||||
|
before(:each) do
|
||||||
|
allow(redis).to receive(:hset)
|
||||||
|
allow(redis).to receive(:hdel)
|
||||||
|
allow(provider).to receive(:create_template_delta_disks)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should mark the pool as updating' do
|
||||||
|
expect(redis).to receive(:hset).with('vmpooler__config__updating', pool, 1)
|
||||||
|
|
||||||
|
subject.prepare_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should run create template delta disks' do
|
||||||
|
expect(provider).to receive(:create_template_delta_disks).with(config[:pools][0])
|
||||||
|
|
||||||
|
subject.prepare_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should mark the template as prepared' do
|
||||||
|
expect(redis).to receive(:hset).with('vmpooler__template__prepared', pool, config[:pools][0]['template'])
|
||||||
|
|
||||||
|
subject.prepare_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
it' should mark the configuration as completed' do
|
||||||
|
expect(redis).to receive(:hdel).with('vmpooler__config__updating', pool)
|
||||||
|
|
||||||
|
subject.prepare_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#execute!" do
|
describe "#execute!" do
|
||||||
let(:config) {
|
let(:config) {
|
||||||
YAML.load(<<-EOT
|
YAML.load(<<-EOT
|
||||||
|
|
@ -1824,6 +2038,8 @@ EOT
|
||||||
it 'should run startup tasks only once' do
|
it 'should run startup tasks only once' do
|
||||||
expect(redis).to receive(:set).with('vmpooler__tasks__clone', 0).once
|
expect(redis).to receive(:set).with('vmpooler__tasks__clone', 0).once
|
||||||
expect(redis).to receive(:del).with('vmpooler__migration').once
|
expect(redis).to receive(:del).with('vmpooler__migration').once
|
||||||
|
expect(redis).to receive(:del).with('vmpooler__config__updating').once
|
||||||
|
expect(redis).to receive(:del).with('vmpooler__template__prepared').once
|
||||||
|
|
||||||
subject.execute!(maxloop,0)
|
subject.execute!(maxloop,0)
|
||||||
end
|
end
|
||||||
|
|
@ -2785,6 +3001,54 @@ EOT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when a pool size configuration change is detected' do
|
||||||
|
let(:poolsize) { 2 }
|
||||||
|
let(:newpoolsize) { 3 }
|
||||||
|
before(:each) do
|
||||||
|
config[:pools][0]['size'] = poolsize
|
||||||
|
redis.hset('vmpooler__config__poolsize', pool, newpoolsize)
|
||||||
|
expect(provider).to receive(:vms_in_pool).with(pool).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should change the pool size configuration' do
|
||||||
|
subject._check_pool(config[:pools][0],provider)
|
||||||
|
|
||||||
|
expect(config[:pools][0]['size']).to be(newpoolsize)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a pool template is updating' do
|
||||||
|
before(:each) do
|
||||||
|
redis.hset('vmpooler__config__updating', pool, 1)
|
||||||
|
expect(provider).to receive(:vms_in_pool).with(pool).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not call clone_vm to populate the pool' do
|
||||||
|
pool_size = 5
|
||||||
|
config[:pools][0]['size'] = pool_size
|
||||||
|
|
||||||
|
expect(subject).to_not receive(:clone_vm)
|
||||||
|
|
||||||
|
subject._check_pool(pool_object,provider)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when an excess number of ready vms exist' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(redis).to receive(:scard)
|
||||||
|
expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(1)
|
||||||
|
expect(redis).to receive(:scard).with("vmpooler__pending__#{pool}").and_return(1)
|
||||||
|
expect(provider).to receive(:vms_in_pool).with(pool).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should call remove_excess_vms' do
|
||||||
|
expect(subject).to receive(:remove_excess_vms).with(config[:pools][0], provider, 1, 2)
|
||||||
|
|
||||||
|
subject._check_pool(config[:pools][0],provider)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'export metrics' do
|
context 'export metrics' do
|
||||||
it 'increments metrics for ready queue' do
|
it 'increments metrics for ready queue' do
|
||||||
create_ready_vm(pool,'vm1')
|
create_ready_vm(pool,'vm1')
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,7 @@ EOT
|
||||||
|
|
||||||
let(:clone_vm_task) { mock_RbVmomi_VIM_Task() }
|
let(:clone_vm_task) { mock_RbVmomi_VIM_Task() }
|
||||||
let(:new_vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
|
let(:new_vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
|
||||||
|
let(:new_template_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
|
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
|
||||||
|
|
@ -305,19 +306,10 @@ EOT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'Given a template path that does not exist' do
|
|
||||||
before(:each) do
|
|
||||||
config[:pools][0]['template'] = 'missing_Templates/pool1'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should raise an error' do
|
|
||||||
expect{ subject.create_vm(poolname, vmname) }.to raise_error(/specifies a template folder of .+ which does not exist/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'Given a template VM that does not exist' do
|
context 'Given a template VM that does not exist' do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
config[:pools][0]['template'] = 'Templates/missing_template'
|
config[:pools][0]['template'] = 'Templates/missing_template'
|
||||||
|
expect(subject).to receive(:find_template_vm).and_raise("specifies a template VM of #{vmname} which does not exist")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should raise an error' do
|
it 'should raise an error' do
|
||||||
|
|
@ -327,7 +319,8 @@ EOT
|
||||||
|
|
||||||
context 'Given a successful creation' do
|
context 'Given a successful creation' do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
template_vm = subject.find_folder('Templates',connection,datacenter_name).find('pool1')
|
template_vm = new_template_object
|
||||||
|
allow(subject).to receive(:find_template_vm).and_return(new_template_object)
|
||||||
allow(template_vm).to receive(:CloneVM_Task).and_return(clone_vm_task)
|
allow(template_vm).to receive(:CloneVM_Task).and_return(clone_vm_task)
|
||||||
allow(clone_vm_task).to receive(:wait_for_completion).and_return(new_vm_object)
|
allow(clone_vm_task).to receive(:wait_for_completion).and_return(new_vm_object)
|
||||||
end
|
end
|
||||||
|
|
@ -339,7 +332,7 @@ EOT
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should use the appropriate Create_VM spec' do
|
it 'should use the appropriate Create_VM spec' do
|
||||||
template_vm = subject.find_folder('Templates',connection,datacenter_name).find('pool1')
|
template_vm = new_template_object
|
||||||
expect(template_vm).to receive(:CloneVM_Task)
|
expect(template_vm).to receive(:CloneVM_Task)
|
||||||
.with(create_vm_spec(vmname,'pool1','datastore0'))
|
.with(create_vm_spec(vmname,'pool1','datastore0'))
|
||||||
.and_return(clone_vm_task)
|
.and_return(clone_vm_task)
|
||||||
|
|
@ -3578,5 +3571,52 @@ EOT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'find_template_vm' do
|
||||||
|
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine() }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(connection.searchIndex).to receive(:FindByInventoryPath)
|
||||||
|
end
|
||||||
|
it 'should raise an error when the datacenter cannot be found' do
|
||||||
|
config[:providers][:vsphere]['datacenter'] = nil
|
||||||
|
|
||||||
|
expect{ subject.find_template_vm(config[:pools][0],connection) }.to raise_error('cannot find datacenter')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should raise an error when the template specified cannot be found' do
|
||||||
|
expect(connection.searchIndex).to receive(:FindByInventoryPath).and_return(nil)
|
||||||
|
|
||||||
|
expect{ subject.find_template_vm(config[:pools][0],connection) }.to raise_error("Pool #{poolname} specifies a template VM of #{config[:pools][0]['template']} which does not exist for the provider vsphere")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return the vm object' do
|
||||||
|
expect(connection.searchIndex).to receive(:FindByInventoryPath).and_return(vm_object)
|
||||||
|
|
||||||
|
subject.find_template_vm(config[:pools][0],connection)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'create_template_delta_disks' do
|
||||||
|
let(:template_object) { mock_RbVmomi_VIM_VirtualMachine({
|
||||||
|
:name => vmname,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a template VM found' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
expect(subject).to receive(:find_template_vm).and_return(template_object)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should reconfigure the VM creating delta disks' do
|
||||||
|
expect(template_object).to receive(:add_delta_disk_layer_on_all_disks)
|
||||||
|
|
||||||
|
subject.create_template_delta_disks(config[:pools][0])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue