mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-27 02:18:41 -05:00
Resolve merge conflicts from upstream rebase
This commit is contained in:
commit
37e137962a
17 changed files with 1812 additions and 269 deletions
115
docs/API.md
115
docs/API.md
|
|
@ -1,8 +1,17 @@
|
||||||
### API
|
# Table of contents
|
||||||
|
1. [API](#API)
|
||||||
|
2. [Token operations](#token)
|
||||||
|
3. [VM operations](#vmops)
|
||||||
|
4. [Add disks](#adddisks)
|
||||||
|
5. [VM snapshots](#vmsnapshots)
|
||||||
|
6. [Status and metrics](#statusmetrics)
|
||||||
|
7. [Pool configuration](#poolconfig)
|
||||||
|
|
||||||
|
### API <a name="API"></a>
|
||||||
|
|
||||||
vmpooler provides a REST API for VM management. The following examples use `curl` for communication.
|
vmpooler provides a REST API for VM management. The following examples use `curl` for communication.
|
||||||
|
|
||||||
#### Token operations
|
#### Token operations <a name="token"></a>
|
||||||
|
|
||||||
Token-based authentication can be used when requesting or modifying VMs. The `/token` route can be used to create, query, or delete tokens. See the provided YAML configuration example, [vmpooler.yaml.example](vmpooler.yaml.example), for information on configuring an authentication store to use when performing token operations.
|
Token-based authentication can be used when requesting or modifying VMs. The `/token` route can be used to create, query, or delete tokens. See the provided YAML configuration example, [vmpooler.yaml.example](vmpooler.yaml.example), for information on configuring an authentication store to use when performing token operations.
|
||||||
|
|
||||||
|
|
@ -76,7 +85,7 @@ Enter host password for user 'jdoe':
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### VM operations
|
#### VM operations <a name="vmops"></a>
|
||||||
|
|
||||||
##### GET /vm
|
##### GET /vm
|
||||||
|
|
||||||
|
|
@ -230,7 +239,7 @@ $ curl -X DELETE --url vmpooler.company.com/api/v1/vm/fq6qlpjlsskycq6
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Adding additional disk(s)
|
#### Adding additional disk(s) <a name="adddisks"></a>
|
||||||
|
|
||||||
##### POST /vm/<hostname>/disk/<size>
|
##### POST /vm/<hostname>/disk/<size>
|
||||||
|
|
||||||
|
|
@ -270,7 +279,7 @@ $ curl --url vmpooler.company.com/api/v1/vm/fq6qlpjlsskycq6
|
||||||
|
|
||||||
````
|
````
|
||||||
|
|
||||||
#### VM snapshots
|
#### VM snapshots <a name="vmsnapshots"></a>
|
||||||
|
|
||||||
##### POST /vm/<hostname>/snapshot
|
##### POST /vm/<hostname>/snapshot
|
||||||
|
|
||||||
|
|
@ -322,7 +331,7 @@ $ curl X POST -H X-AUTH-TOKEN:a9znth9dn01t416hrguu56ze37t790bl --url vmpooler.co
|
||||||
}
|
}
|
||||||
````
|
````
|
||||||
|
|
||||||
#### Status and metrics
|
#### Status and metrics <a name="statusmetrics"></a>
|
||||||
|
|
||||||
##### GET /status
|
##### GET /status
|
||||||
|
|
||||||
|
|
@ -540,3 +549,97 @@ $ curl -G -d 'from=2015-03-10' -d 'to=2015-03-11' --url vmpooler.company.com/api
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Managing pool configuration via API <a name="poolconfig"></a>
|
||||||
|
|
||||||
|
##### GET /config
|
||||||
|
|
||||||
|
Returns the running pool configuration
|
||||||
|
|
||||||
|
Responses:
|
||||||
|
* 200 - OK
|
||||||
|
* 404 - No configuration found
|
||||||
|
```
|
||||||
|
$ curl https://vmpooler.company.com/api/v1/config
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pool_configuration": [
|
||||||
|
{
|
||||||
|
"name": "redhat-7-x86_64",
|
||||||
|
"template": "templates/redhat-7.2-x86_64-0.0.3",
|
||||||
|
"folder": "vmpooler/redhat-7-x86_64",
|
||||||
|
"datastore": "stor1",
|
||||||
|
"size": 1,
|
||||||
|
"datacenter": "dc1",
|
||||||
|
"provider": "vsphere",
|
||||||
|
"capacity": 1,
|
||||||
|
"major": "redhat",
|
||||||
|
"template_ready": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": {
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: to enable poolsize and pooltemplate config endpoints it is necessary to set 'experimental_features: true' in your vmpooler configuration. A 405 is returned when you attempt to interact with these endpoints when this configuration option is not set.
|
||||||
|
|
||||||
|
##### 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
|
||||||
|
* 400 - An invalid configuration was provided causing requested changes to fail
|
||||||
|
* 404 - An unknown error occurred
|
||||||
|
* 405 - The endpoint is disabled because experimental features are disabled
|
||||||
|
```
|
||||||
|
$ 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
|
||||||
|
* 400 - An invalid configuration was provided causing requested changes to fail
|
||||||
|
* 404 - An unknown error occurred
|
||||||
|
* 405 - The endpoint is disabled because experimental features are disabled
|
||||||
|
```
|
||||||
|
$ 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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ module Vmpooler
|
||||||
require 'date'
|
require 'date'
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'open-uri'
|
require 'open-uri'
|
||||||
|
require 'net/ldap'
|
||||||
require 'rbvmomi'
|
require 'rbvmomi'
|
||||||
require 'redis'
|
require 'redis'
|
||||||
require 'sinatra/base'
|
require 'sinatra/base'
|
||||||
|
|
|
||||||
|
|
@ -54,36 +54,61 @@ module Vmpooler
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authenticate_ldap(port, host, user_object, base, username_str, password_str)
|
||||||
|
ldap = Net::LDAP.new(
|
||||||
|
:host => host,
|
||||||
|
:port => port,
|
||||||
|
:encryption => {
|
||||||
|
:method => :start_tls,
|
||||||
|
:tls_options => { :ssl_version => 'TLSv1' }
|
||||||
|
},
|
||||||
|
:base => base,
|
||||||
|
:auth => {
|
||||||
|
:method => :simple,
|
||||||
|
:username => "#{user_object}=#{username_str},#{base}",
|
||||||
|
:password => password_str
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return true if ldap.bind
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
def authenticate(auth, username_str, password_str)
|
def authenticate(auth, username_str, password_str)
|
||||||
case auth['provider']
|
case auth['provider']
|
||||||
when 'dummy'
|
when 'dummy'
|
||||||
return (username_str != password_str)
|
return (username_str != password_str)
|
||||||
when 'ldap'
|
when 'ldap'
|
||||||
require 'rubygems'
|
ldap_base = auth[:ldap]['base']
|
||||||
require 'net/ldap'
|
ldap_port = auth[:ldap]['port'] || 389
|
||||||
|
|
||||||
ldap = Net::LDAP.new(
|
if ldap_base.is_a? Array
|
||||||
:host => auth[:ldap]['host'],
|
ldap_base.each do |search_base|
|
||||||
:port => auth[:ldap]['port'] || 389,
|
result = authenticate_ldap(
|
||||||
:encryption => {
|
ldap_port,
|
||||||
:method => :start_tls,
|
auth[:ldap]['host'],
|
||||||
:tls_options => { :ssl_version => 'TLSv1' }
|
auth[:ldap]['user_object'],
|
||||||
},
|
search_base,
|
||||||
:base => auth[:ldap]['base'],
|
username_str,
|
||||||
:auth => {
|
password_str,
|
||||||
:method => :simple,
|
|
||||||
:username => "#{auth[:ldap]['user_object']}=#{username_str},#{auth[:ldap]['base']}",
|
|
||||||
:password => password_str
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
return true if result == true
|
||||||
if ldap.bind
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
result = authenticate_ldap(
|
||||||
|
ldap_port,
|
||||||
|
auth[:ldap]['host'],
|
||||||
|
auth[:ldap]['user_object'],
|
||||||
|
ldap_base,
|
||||||
|
username_str,
|
||||||
|
password_str,
|
||||||
|
)
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def export_tags(backend, hostname, tags)
|
def export_tags(backend, hostname, tags)
|
||||||
tags.each_pair do |tag, value|
|
tags.each_pair do |tag, value|
|
||||||
|
|
@ -378,6 +403,29 @@ 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
|
||||||
|
|
||||||
|
def template_ready?(pool, backend)
|
||||||
|
prepared_template = backend.hget('vmpooler__template__prepared', pool['name'])
|
||||||
|
return false if prepared_template.nil?
|
||||||
|
return true if pool['template'] == prepared_template
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_integer?(x)
|
||||||
|
Integer(x)
|
||||||
|
true
|
||||||
|
rescue
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,74 @@ module Vmpooler
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_pool_size(payload)
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
pool_index = pool_index(pools)
|
||||||
|
pools_updated = 0
|
||||||
|
sync_pool_sizes
|
||||||
|
|
||||||
|
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
|
||||||
|
sync_pool_templates
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def sync_pool_templates
|
||||||
|
pool_index = pool_index(pools)
|
||||||
|
template_configs = backend.hgetall('vmpooler__config__template')
|
||||||
|
unless template_configs.nil?
|
||||||
|
template_configs.each do |poolname, template|
|
||||||
|
if pool_index.include? poolname
|
||||||
|
unless pools[pool_index[poolname]]['template'] == template
|
||||||
|
pools[pool_index[poolname]]['template'] = template
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_pool_sizes
|
||||||
|
pool_index = pool_index(pools)
|
||||||
|
poolsize_configs = backend.hgetall('vmpooler__config__poolsize')
|
||||||
|
unless poolsize_configs.nil?
|
||||||
|
poolsize_configs.each do |poolname, size|
|
||||||
|
if pool_index.include? poolname
|
||||||
|
unless pools[pool_index[poolname]]['size'] == size.to_i
|
||||||
|
pools[pool_index[poolname]]['size'] == size.to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Provide run-time statistics
|
# Provide run-time statistics
|
||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
|
|
@ -196,6 +264,8 @@ module Vmpooler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sync_pool_sizes
|
||||||
|
|
||||||
result[:capacity] = get_capacity_metrics(pools, backend) unless views and not views.include?("capacity")
|
result[:capacity] = get_capacity_metrics(pools, backend) unless views and not views.include?("capacity")
|
||||||
result[:queue] = get_queue_metrics(pools, backend) unless views and not views.include?("queue")
|
result[:queue] = get_queue_metrics(pools, backend) unless views and not views.include?("queue")
|
||||||
result[:clone] = get_task_metrics(backend, 'clone', Date.today.to_s) unless views and not views.include?("clone")
|
result[:clone] = get_task_metrics(backend, 'clone', Date.today.to_s) unless views and not views.include?("clone")
|
||||||
|
|
@ -502,6 +572,30 @@ module Vmpooler
|
||||||
invalid
|
invalid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def invalid_template_or_size(payload)
|
||||||
|
invalid = []
|
||||||
|
payload.each do |pool, size|
|
||||||
|
invalid << pool unless pool_exists?(pool)
|
||||||
|
unless is_integer?(size)
|
||||||
|
invalid << pool
|
||||||
|
next
|
||||||
|
end
|
||||||
|
invalid << pool unless Integer(size) >= 0
|
||||||
|
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? '/'
|
||||||
|
invalid << pool if template[0] == '/'
|
||||||
|
invalid << pool if template[-1] == '/'
|
||||||
|
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 +841,95 @@ module Vmpooler
|
||||||
|
|
||||||
JSON.pretty_generate(result)
|
JSON.pretty_generate(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post "#{api_prefix}/config/poolsize/?" do
|
||||||
|
content_type :json
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
if config['experimental_features']
|
||||||
|
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
|
||||||
|
result[:bad_templates] = invalid
|
||||||
|
status 400
|
||||||
|
end
|
||||||
|
else
|
||||||
|
metrics.increment('config.invalid.unknown')
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
else
|
||||||
|
status 405
|
||||||
|
end
|
||||||
|
|
||||||
|
JSON.pretty_generate(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
post "#{api_prefix}/config/pooltemplate/?" do
|
||||||
|
content_type :json
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
if config['experimental_features']
|
||||||
|
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
|
||||||
|
result[:bad_templates] = invalid
|
||||||
|
status 400
|
||||||
|
end
|
||||||
|
else
|
||||||
|
metrics.increment('config.invalid.unknown')
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
else
|
||||||
|
status 405
|
||||||
|
end
|
||||||
|
|
||||||
|
JSON.pretty_generate(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
get "#{api_prefix}/config/?" do
|
||||||
|
content_type :json
|
||||||
|
result = { 'ok' => false }
|
||||||
|
status 404
|
||||||
|
|
||||||
|
if pools
|
||||||
|
sync_pool_sizes
|
||||||
|
sync_pool_templates
|
||||||
|
|
||||||
|
pool_configuration = []
|
||||||
|
pools.each do |pool|
|
||||||
|
pool['template_ready'] = template_ready?(pool, backend)
|
||||||
|
pool_configuration << pool
|
||||||
|
end
|
||||||
|
|
||||||
|
result = {
|
||||||
|
pool_configuration: pool_configuration,
|
||||||
|
status: {
|
||||||
|
ok: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status 200
|
||||||
|
end
|
||||||
|
JSON.pretty_generate(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ module Vmpooler
|
||||||
|
|
||||||
# Our thread-tracker object
|
# Our thread-tracker object
|
||||||
$threads = {}
|
$threads = {}
|
||||||
|
|
||||||
|
# Pool mutex
|
||||||
|
@reconfigure_pool = {}
|
||||||
|
|
||||||
|
@vm_mutex = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def config
|
def config
|
||||||
|
|
@ -41,6 +46,9 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
|
|
||||||
def _check_pending_vm(vm, pool, timeout, provider)
|
def _check_pending_vm(vm, pool, timeout, provider)
|
||||||
|
mutex = vm_mutex(vm)
|
||||||
|
return if mutex.locked?
|
||||||
|
mutex.synchronize do
|
||||||
host = provider.get_vm(pool, vm)
|
host = provider.get_vm(pool, vm)
|
||||||
unless host
|
unless host
|
||||||
fail_pending_vm(vm, pool, timeout, false)
|
fail_pending_vm(vm, pool, timeout, false)
|
||||||
|
|
@ -52,6 +60,7 @@ module Vmpooler
|
||||||
fail_pending_vm(vm, pool, timeout)
|
fail_pending_vm(vm, pool, timeout)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def remove_nonexistent_vm(vm, pool)
|
def remove_nonexistent_vm(vm, pool)
|
||||||
$redis.srem("vmpooler__pending__#{pool}", vm)
|
$redis.srem("vmpooler__pending__#{pool}", vm)
|
||||||
|
|
@ -111,6 +120,9 @@ module Vmpooler
|
||||||
|
|
||||||
def _check_ready_vm(vm, pool, ttl, provider)
|
def _check_ready_vm(vm, pool, ttl, provider)
|
||||||
# Periodically check that the VM is available
|
# Periodically check that the VM is available
|
||||||
|
mutex = vm_mutex(vm)
|
||||||
|
return if mutex.locked?
|
||||||
|
mutex.synchronize do
|
||||||
check_stamp = $redis.hget('vmpooler__vm__' + vm, 'check')
|
check_stamp = $redis.hget('vmpooler__vm__' + vm, 'check')
|
||||||
return if check_stamp && (((Time.now - Time.parse(check_stamp)) / 60) <= $config[:config]['vm_checktime'])
|
return if check_stamp && (((Time.now - Time.parse(check_stamp)) / 60) <= $config[:config]['vm_checktime'])
|
||||||
|
|
||||||
|
|
@ -159,6 +171,7 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def check_running_vm(vm, pool, ttl, provider)
|
def check_running_vm(vm, pool, ttl, provider)
|
||||||
Thread.new do
|
Thread.new do
|
||||||
|
|
@ -172,6 +185,9 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
|
|
||||||
def _check_running_vm(vm, pool, ttl, provider)
|
def _check_running_vm(vm, pool, ttl, provider)
|
||||||
|
mutex = vm_mutex(vm)
|
||||||
|
return if mutex.locked?
|
||||||
|
mutex.synchronize do
|
||||||
host = provider.get_vm(pool, vm)
|
host = provider.get_vm(pool, vm)
|
||||||
|
|
||||||
if host
|
if host
|
||||||
|
|
@ -186,10 +202,11 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
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
|
||||||
|
|
@ -248,6 +265,9 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
|
|
||||||
def _destroy_vm(vm, pool, provider)
|
def _destroy_vm(vm, pool, provider)
|
||||||
|
mutex = vm_mutex(vm)
|
||||||
|
return if mutex.locked?
|
||||||
|
mutex.synchronize do
|
||||||
$redis.srem('vmpooler__completed__' + pool, vm)
|
$redis.srem('vmpooler__completed__' + pool, vm)
|
||||||
$redis.hdel('vmpooler__active__' + pool, vm)
|
$redis.hdel('vmpooler__active__' + pool, vm)
|
||||||
$redis.hset('vmpooler__vm__' + vm, 'destroy', Time.now)
|
$redis.hset('vmpooler__vm__' + vm, 'destroy', Time.now)
|
||||||
|
|
@ -263,6 +283,7 @@ module Vmpooler
|
||||||
$logger.log('s', "[-] [#{pool}] '#{vm}' destroyed in #{finish} seconds")
|
$logger.log('s', "[-] [#{pool}] '#{vm}' destroyed in #{finish} seconds")
|
||||||
$metrics.timing("destroy.#{pool}", finish)
|
$metrics.timing("destroy.#{pool}", finish)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def create_vm_disk(pool_name, vm, disk_size, provider)
|
def create_vm_disk(pool_name, vm, disk_size, provider)
|
||||||
Thread.new do
|
Thread.new do
|
||||||
|
|
@ -464,8 +485,11 @@ module Vmpooler
|
||||||
def migrate_vm(vm_name, pool_name, provider)
|
def migrate_vm(vm_name, pool_name, provider)
|
||||||
Thread.new do
|
Thread.new do
|
||||||
begin
|
begin
|
||||||
|
mutex = vm_mutex(vm_name)
|
||||||
|
mutex.synchronize do
|
||||||
$redis.srem("vmpooler__migrating__#{pool_name}", vm_name)
|
$redis.srem("vmpooler__migrating__#{pool_name}", vm_name)
|
||||||
provider.migrate_vm(pool_name, vm_name)
|
provider.migrate_vm(pool_name, vm_name)
|
||||||
|
end
|
||||||
rescue => err
|
rescue => err
|
||||||
$logger.log('s', "[x] [#{pool_name}] '#{vm_name}' migration failed with an error: #{err}")
|
$logger.log('s', "[x] [#{pool_name}] '#{vm_name}' migration failed with an error: #{err}")
|
||||||
end
|
end
|
||||||
|
|
@ -482,6 +506,10 @@ module Vmpooler
|
||||||
# - Fires when the number of ready VMs changes due to being consumed.
|
# - Fires when the number of ready VMs changes due to being consumed.
|
||||||
# - Additional options
|
# - Additional options
|
||||||
# :poolname
|
# :poolname
|
||||||
|
# :pool_template_change
|
||||||
|
# - Fires when a template configuration update is requested
|
||||||
|
# - Additional options
|
||||||
|
# :poolname
|
||||||
#
|
#
|
||||||
def sleep_with_wakeup_events(loop_delay, wakeup_period = 5, options = {})
|
def sleep_with_wakeup_events(loop_delay, wakeup_period = 5, options = {})
|
||||||
exit_by = Time.now + loop_delay
|
exit_by = Time.now + loop_delay
|
||||||
|
|
@ -492,6 +520,10 @@ module Vmpooler
|
||||||
initial_ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}")
|
initial_ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if options[:pool_template_change]
|
||||||
|
initial_template = $redis.hget('vmpooler__template__prepared', options[:poolname])
|
||||||
|
end
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
sleep(1)
|
sleep(1)
|
||||||
break if time_passed?(:exit_by, exit_by)
|
break if time_passed?(:exit_by, exit_by)
|
||||||
|
|
@ -505,6 +537,14 @@ module Vmpooler
|
||||||
ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}")
|
ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}")
|
||||||
break unless ready_size == initial_ready_size
|
break unless ready_size == initial_ready_size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if options[:pool_template_change]
|
||||||
|
configured_template = $redis.hget('vmpooler__config__template', options[:poolname])
|
||||||
|
if configured_template
|
||||||
|
break unless initial_template == configured_template
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
break if time_passed?(:exit_by, exit_by)
|
break if time_passed?(:exit_by, exit_by)
|
||||||
|
|
@ -532,6 +572,7 @@ module Vmpooler
|
||||||
loop_delay = loop_delay_min
|
loop_delay = loop_delay_min
|
||||||
provider = get_provider_for_pool(pool['name'])
|
provider = get_provider_for_pool(pool['name'])
|
||||||
raise("Could not find provider '#{pool['provider']}") if provider.nil?
|
raise("Could not find provider '#{pool['provider']}") if provider.nil?
|
||||||
|
sync_pool_template(pool)
|
||||||
loop do
|
loop do
|
||||||
result = _check_pool(pool, provider)
|
result = _check_pool(pool, provider)
|
||||||
|
|
||||||
|
|
@ -541,7 +582,7 @@ module Vmpooler
|
||||||
loop_delay = (loop_delay * loop_delay_decay).to_i
|
loop_delay = (loop_delay * loop_delay_decay).to_i
|
||||||
loop_delay = loop_delay_max if loop_delay > loop_delay_max
|
loop_delay = loop_delay_max if loop_delay > loop_delay_max
|
||||||
end
|
end
|
||||||
sleep_with_wakeup_events(loop_delay, loop_delay_min, pool_size_change: true, poolname: pool['name'])
|
sleep_with_wakeup_events(loop_delay, loop_delay_min, pool_size_change: true, poolname: pool['name'], pool_template_change: true)
|
||||||
|
|
||||||
unless maxloop.zero?
|
unless maxloop.zero?
|
||||||
break if loop_count >= maxloop
|
break if loop_count >= maxloop
|
||||||
|
|
@ -555,6 +596,105 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pool_mutex(poolname)
|
||||||
|
@reconfigure_pool[poolname] || @reconfigure_pool[poolname] = Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def vm_mutex(vmname)
|
||||||
|
@vm_mutex[vmname] || @vm_mutex[vmname] = Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_pool_template(pool)
|
||||||
|
pool_template = $redis.hget('vmpooler__config__template', pool['name'])
|
||||||
|
if pool_template
|
||||||
|
unless pool['template'] == pool_template
|
||||||
|
pool['template'] = pool_template
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_template(pool, provider)
|
||||||
|
provider.create_template_delta_disks(pool) if $config[:config]['create_template_delta_disks']
|
||||||
|
$redis.hset('vmpooler__template__prepared', pool['name'], pool['template'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate_template(pool, provider)
|
||||||
|
mutex = pool_mutex(pool['name'])
|
||||||
|
prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
|
||||||
|
configured_template = $redis.hget('vmpooler__config__template', pool['name'])
|
||||||
|
return if mutex.locked?
|
||||||
|
if prepared_template.nil?
|
||||||
|
mutex.synchronize do
|
||||||
|
prepare_template(pool, provider)
|
||||||
|
prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return if configured_template.nil?
|
||||||
|
return if configured_template == prepared_template
|
||||||
|
mutex.synchronize do
|
||||||
|
update_pool_template(pool, provider, configured_template, prepared_template)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def drain_pool(poolname)
|
||||||
|
# Clear a pool of ready and pending instances
|
||||||
|
if $redis.scard("vmpooler__ready__#{poolname}") > 0
|
||||||
|
$logger.log('s', "[*] [#{poolname}] removing ready instances")
|
||||||
|
$redis.smembers("vmpooler__ready__#{poolname}").each do |vm|
|
||||||
|
move_vm_queue(poolname, vm, 'ready', 'completed')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if $redis.scard("vmpooler__pending__#{poolname}") > 0
|
||||||
|
$logger.log('s', "[*] [#{poolname}] removing pending instances")
|
||||||
|
$redis.smembers("vmpooler__pending__#{poolname}").each do |vm|
|
||||||
|
move_vm_queue(poolname, vm, 'pending', 'completed')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_pool_template(pool, provider, configured_template, prepared_template)
|
||||||
|
pool['template'] = configured_template
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] template updated from #{prepared_template} to #{configured_template}")
|
||||||
|
# Remove all ready and pending VMs so new instances are created from the new template
|
||||||
|
drain_pool(pool['name'])
|
||||||
|
# Prepare template for deployment
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] preparing pool template for deployment")
|
||||||
|
prepare_template(pool, provider)
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] is ready for use")
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_excess_vms(pool, provider, ready, total)
|
||||||
|
return if total.nil?
|
||||||
|
return if total == 0
|
||||||
|
mutex = pool_mutex(pool['name'])
|
||||||
|
return if mutex.locked?
|
||||||
|
return unless ready > pool['size']
|
||||||
|
mutex.synchronize do
|
||||||
|
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
|
||||||
|
|
||||||
|
def update_pool_size(pool)
|
||||||
|
mutex = pool_mutex(pool['name'])
|
||||||
|
return if mutex.locked?
|
||||||
|
poolsize = $redis.hget('vmpooler__config__poolsize', pool['name'])
|
||||||
|
return if poolsize.nil?
|
||||||
|
poolsize = Integer(poolsize)
|
||||||
|
return if poolsize == pool['size']
|
||||||
|
mutex.synchronize do
|
||||||
|
pool['size'] = poolsize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def create_inventory(pool, provider, pool_check_response)
|
def create_inventory(pool, provider, pool_check_response)
|
||||||
inventory = {}
|
inventory = {}
|
||||||
begin
|
begin
|
||||||
|
|
@ -702,7 +842,15 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# UPDATE TEMPLATE
|
||||||
|
# Evaluates a pool template to ensure templates are prepared adequately for the configured provider
|
||||||
|
# If a pool template configuration change is detected then template preparation is repeated for the new template
|
||||||
|
# Additionally, a pool will drain ready and pending instances
|
||||||
|
evaluate_template(pool, provider)
|
||||||
|
|
||||||
# REPOPULATE
|
# REPOPULATE
|
||||||
|
# Do not attempt to repopulate a pool while a template is updating
|
||||||
|
unless pool_mutex(pool['name']).locked?
|
||||||
ready = $redis.scard("vmpooler__ready__#{pool['name']}")
|
ready = $redis.scard("vmpooler__ready__#{pool['name']}")
|
||||||
total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
|
total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
|
||||||
|
|
||||||
|
|
@ -716,6 +864,11 @@ module Vmpooler
|
||||||
$logger.log('s', "[!] [#{pool['name']}] is empty")
|
$logger.log('s', "[!] [#{pool['name']}] is empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check to see if a pool size 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_size(pool)
|
||||||
|
|
||||||
if total < pool['size']
|
if total < pool['size']
|
||||||
(1..(pool['size'] - total)).each do |_i|
|
(1..(pool['size'] - total)).each do |_i|
|
||||||
if $redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
|
if $redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
|
||||||
|
|
@ -731,6 +884,10 @@ module Vmpooler
|
||||||
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
|
||||||
|
|
@ -758,6 +915,8 @@ 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')
|
||||||
|
# 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 valid_template_path? 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
|
||||||
|
|
@ -410,7 +403,7 @@ module Vmpooler
|
||||||
connection = RbVmomi::VIM.connect host: provider_config['server'],
|
connection = RbVmomi::VIM.connect host: provider_config['server'],
|
||||||
user: provider_config['username'],
|
user: provider_config['username'],
|
||||||
password: provider_config['password'],
|
password: provider_config['password'],
|
||||||
insecure: provider_config['insecure'] || true
|
insecure: provider_config['insecure'] || false
|
||||||
metrics.increment('connect.open')
|
metrics.increment('connect.open')
|
||||||
return connection
|
return connection
|
||||||
rescue => err
|
rescue => err
|
||||||
|
|
@ -465,9 +458,12 @@ module Vmpooler
|
||||||
|
|
||||||
vmdk_datastore = find_datastore(datastore, connection, datacentername)
|
vmdk_datastore = find_datastore(datastore, connection, datacentername)
|
||||||
raise("Datastore '#{datastore}' does not exist in datacenter '#{datacentername}'") if vmdk_datastore.nil?
|
raise("Datastore '#{datastore}' does not exist in datacenter '#{datacentername}'") if vmdk_datastore.nil?
|
||||||
vmdk_file_name = "#{vm['name']}/#{vm['name']}_#{find_vmdks(vm['name'], datastore, connection, datacentername).length + 1}.vmdk"
|
|
||||||
|
|
||||||
|
datacenter = connection.serviceInstance.find_datacenter(datacentername)
|
||||||
controller = find_disk_controller(vm)
|
controller = find_disk_controller(vm)
|
||||||
|
disk_unit_number = find_disk_unit_number(vm, controller)
|
||||||
|
disk_count = vm.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk).count
|
||||||
|
vmdk_file_name = "#{vm['name']}/#{vm['name']}_#{disk_count}.vmdk"
|
||||||
|
|
||||||
vmdk_spec = RbVmomi::VIM::FileBackedVirtualDiskSpec(
|
vmdk_spec = RbVmomi::VIM::FileBackedVirtualDiskSpec(
|
||||||
capacityKb: size.to_i * 1024 * 1024,
|
capacityKb: size.to_i * 1024 * 1024,
|
||||||
|
|
@ -478,7 +474,7 @@ module Vmpooler
|
||||||
vmdk_backing = RbVmomi::VIM::VirtualDiskFlatVer2BackingInfo(
|
vmdk_backing = RbVmomi::VIM::VirtualDiskFlatVer2BackingInfo(
|
||||||
datastore: vmdk_datastore,
|
datastore: vmdk_datastore,
|
||||||
diskMode: DISK_MODE,
|
diskMode: DISK_MODE,
|
||||||
fileName: "[#{vmdk_datastore.name}] #{vmdk_file_name}"
|
fileName: "[#{datastore}] #{vmdk_file_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
device = RbVmomi::VIM::VirtualDisk(
|
device = RbVmomi::VIM::VirtualDisk(
|
||||||
|
|
@ -486,7 +482,7 @@ module Vmpooler
|
||||||
capacityInKB: size.to_i * 1024 * 1024,
|
capacityInKB: size.to_i * 1024 * 1024,
|
||||||
controllerKey: controller.key,
|
controllerKey: controller.key,
|
||||||
key: -1,
|
key: -1,
|
||||||
unitNumber: find_disk_unit_number(vm, controller)
|
unitNumber: disk_unit_number
|
||||||
)
|
)
|
||||||
|
|
||||||
device_config_spec = RbVmomi::VIM::VirtualDeviceConfigSpec(
|
device_config_spec = RbVmomi::VIM::VirtualDeviceConfigSpec(
|
||||||
|
|
@ -499,8 +495,8 @@ module Vmpooler
|
||||||
)
|
)
|
||||||
|
|
||||||
connection.serviceContent.virtualDiskManager.CreateVirtualDisk_Task(
|
connection.serviceContent.virtualDiskManager.CreateVirtualDisk_Task(
|
||||||
datacenter: connection.serviceInstance.find_datacenter(datacentername),
|
datacenter: datacenter,
|
||||||
name: "[#{vmdk_datastore.name}] #{vmdk_file_name}",
|
name: "[#{datastore}] #{vmdk_file_name}",
|
||||||
spec: vmdk_spec
|
spec: vmdk_spec
|
||||||
).wait_for_completion
|
).wait_for_completion
|
||||||
|
|
||||||
|
|
@ -809,23 +805,6 @@ module Vmpooler
|
||||||
connection.searchIndex.FindByInventoryPath(propSpecs)
|
connection.searchIndex.FindByInventoryPath(propSpecs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_vmdks(vmname, datastore, connection, datacentername)
|
|
||||||
disks = []
|
|
||||||
|
|
||||||
vmdk_datastore = find_datastore(datastore, connection, datacentername)
|
|
||||||
|
|
||||||
vm_files = 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)
|
def get_base_vm_container_from(connection)
|
||||||
view_manager = connection.serviceContent.viewManager
|
view_manager = connection.serviceContent.viewManager
|
||||||
view_manager.CreateContainerView(
|
view_manager.CreateContainerView(
|
||||||
|
|
@ -933,6 +912,37 @@ 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
|
||||||
|
|
||||||
|
def valid_template_path?(template)
|
||||||
|
return false unless template.include?('/')
|
||||||
|
return false if template[0] == '/'
|
||||||
|
return false if template[-1] == '/'
|
||||||
|
return true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
240
spec/integration/api/v1/config_spec.rb
Normal file
240
spec/integration/api/v1/config_spec.rb
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'rack/test'
|
||||||
|
|
||||||
|
describe Vmpooler::API::V1 do
|
||||||
|
include Rack::Test::Methods
|
||||||
|
|
||||||
|
def app()
|
||||||
|
Vmpooler::API
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:config) {
|
||||||
|
{
|
||||||
|
config: {
|
||||||
|
'site_name' => 'test pooler',
|
||||||
|
'vm_lifetime_auth' => 2,
|
||||||
|
'experimental_features' => true
|
||||||
|
},
|
||||||
|
pools: [
|
||||||
|
{'name' => 'pool1', 'size' => 5, 'template' => 'templates/pool1'},
|
||||||
|
{'name' => 'pool2', 'size' => 10}
|
||||||
|
],
|
||||||
|
statsd: { 'prefix' => 'stats_prefix'},
|
||||||
|
alias: { 'poolone' => 'pool1' },
|
||||||
|
pool_names: [ 'pool1', 'pool2', 'poolone' ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe '/config/pooltemplate' do
|
||||||
|
let(:prefix) { '/api/v1' }
|
||||||
|
let(:metrics) { Vmpooler::DummyStatsd.new }
|
||||||
|
|
||||||
|
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 = 400)
|
||||||
|
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 = 400)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: false,
|
||||||
|
bad_templates: ['pool3']
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 400)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: false,
|
||||||
|
bad_templates: ['pool1']
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when a template starts with /' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"pool1":"/template1"}'
|
||||||
|
expect_json(ok = false, http = 400)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: false,
|
||||||
|
bad_templates: ['pool1']
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when a template ends with /' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"pool1":"template1/"}'
|
||||||
|
expect_json(ok = false, http = 400)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: false,
|
||||||
|
bad_templates: ['pool1']
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with experimental features disabled' do
|
||||||
|
before(:each) do
|
||||||
|
config[:config]['experimental_features'] = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return 405' do
|
||||||
|
post "#{prefix}/config/pooltemplate", '{"pool1":"template/template1"}'
|
||||||
|
expect_json(ok = false, http = 405)
|
||||||
|
|
||||||
|
expected = { ok: false }
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
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 = 400)
|
||||||
|
expected = {
|
||||||
|
ok: false,
|
||||||
|
bad_templates: ['pool10']
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 400)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: false,
|
||||||
|
bad_templates: ['pool1']
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when a negative value is provided for size' do
|
||||||
|
post "#{prefix}/config/poolsize", '{"pool1":"-1"}'
|
||||||
|
expect_json(ok = false, http = 400)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: false,
|
||||||
|
bad_templates: ['pool1']
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with experimental features disabled' do
|
||||||
|
before(:each) do
|
||||||
|
config[:config]['experimental_features'] = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return 405' do
|
||||||
|
post "#{prefix}/config/poolsize", '{"pool1":"1"}'
|
||||||
|
expect_json(ok = false, http = 405)
|
||||||
|
|
||||||
|
expected = { ok: false }
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /config' do
|
||||||
|
let(:prefix) { '/api/v1' }
|
||||||
|
|
||||||
|
it 'returns pool configuration when set' do
|
||||||
|
get "#{prefix}/config"
|
||||||
|
|
||||||
|
expect(last_response.header['Content-Type']).to eq('application/json')
|
||||||
|
result = JSON.parse(last_response.body)
|
||||||
|
expect(result['pool_configuration']).to eq(config[:pools])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,16 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rack/test'
|
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
|
|
||||||
|
|
||||||
def has_set_tag?(vm, tag, value)
|
def has_set_tag?(vm, tag, value)
|
||||||
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
|
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rack/test'
|
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
|
describe Vmpooler::API::V1 do
|
||||||
include Rack::Test::Methods
|
include Rack::Test::Methods
|
||||||
|
|
||||||
|
|
@ -39,7 +29,15 @@ describe Vmpooler::API::V1 do
|
||||||
end
|
end
|
||||||
|
|
||||||
context '(auth configured)' do
|
context '(auth configured)' do
|
||||||
let(:config) { { auth: true } }
|
let(:config) {
|
||||||
|
{
|
||||||
|
auth: {
|
||||||
|
'provider' => 'dummy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:username_str) { 'admin' }
|
||||||
|
let(:password_str) { 's3cr3t' }
|
||||||
|
|
||||||
it 'returns a 401 if not authed' do
|
it 'returns a 401 if not authed' do
|
||||||
get "#{prefix}/token"
|
get "#{prefix}/token"
|
||||||
|
|
@ -69,7 +67,13 @@ describe Vmpooler::API::V1 do
|
||||||
end
|
end
|
||||||
|
|
||||||
context '(auth configured)' do
|
context '(auth configured)' do
|
||||||
let(:config) { { auth: true } }
|
let(:config) {
|
||||||
|
{
|
||||||
|
auth: {
|
||||||
|
'provider' => 'dummy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
it 'returns a 401 if not authed' do
|
it 'returns a 401 if not authed' do
|
||||||
post "#{prefix}/token"
|
post "#{prefix}/token"
|
||||||
|
|
@ -146,7 +150,13 @@ describe Vmpooler::API::V1 do
|
||||||
end
|
end
|
||||||
|
|
||||||
context '(auth configured)' do
|
context '(auth configured)' do
|
||||||
let(:config) { { auth: true } }
|
let(:config) {
|
||||||
|
{
|
||||||
|
auth: {
|
||||||
|
'provider' => 'dummy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
it 'returns a 401 if not authed' do
|
it 'returns a 401 if not authed' do
|
||||||
delete "#{prefix}/token/this"
|
delete "#{prefix}/token/this"
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rack/test'
|
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
|
|
||||||
|
|
||||||
def has_set_tag?(vm, tag, value)
|
def has_set_tag?(vm, tag, value)
|
||||||
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
|
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rack/test'
|
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
|
describe Vmpooler::API::V1 do
|
||||||
include Rack::Test::Methods
|
include Rack::Test::Methods
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rack/test'
|
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
|
describe Vmpooler::API::V1 do
|
||||||
include Rack::Test::Methods
|
include Rack::Test::Methods
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
require 'net/ldap'
|
||||||
|
|
||||||
# A class for testing purposes that includes the Helpers.
|
# A class for testing purposes that includes the Helpers.
|
||||||
# this is impersonating V1's `helpers do include Helpers end`
|
# this is impersonating V1's `helpers do include Helpers end`
|
||||||
|
|
@ -168,4 +169,242 @@ describe Vmpooler::API::Helpers do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#pool_index' do
|
||||||
|
let(:pools) {
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'name' => 'pool1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name' => 'pool2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
it 'should return a hash' do
|
||||||
|
pools_hash = subject.pool_index(pools)
|
||||||
|
|
||||||
|
expect(pools_hash).to be_a(Hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return the correct index for each pool' do
|
||||||
|
pools_hash = subject.pool_index(pools)
|
||||||
|
|
||||||
|
expect(pools[pools_hash['pool1']]['name']).to eq('pool1')
|
||||||
|
expect(pools[pools_hash['pool2']]['name']).to eq('pool2')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#template_ready?' do
|
||||||
|
let(:redis) { double('redis') }
|
||||||
|
let(:template) { 'template/test1' }
|
||||||
|
let(:poolname) { 'pool1' }
|
||||||
|
let(:pool) {
|
||||||
|
{
|
||||||
|
'name' => poolname,
|
||||||
|
'template' => template
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it 'returns false when there is no prepared template' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__template__prepared', poolname).and_return(nil)
|
||||||
|
|
||||||
|
expect(subject.template_ready?(pool, redis)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when configured and prepared templates match' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__template__prepared', poolname).and_return(template)
|
||||||
|
|
||||||
|
expect(subject.template_ready?(pool, redis)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false when configured and prepared templates do not match' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__template__prepared', poolname).and_return('template3')
|
||||||
|
|
||||||
|
expect(subject.template_ready?(pool, redis)).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#is_integer?' do
|
||||||
|
it 'returns true when input is an integer' do
|
||||||
|
expect(subject.is_integer? 4).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when input is a string containing an integer' do
|
||||||
|
expect(subject.is_integer? '4').to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false when input is a string containing word characters' do
|
||||||
|
expect(subject.is_integer? 'four').to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#authenticate' do
|
||||||
|
let(:username_str) { 'admin' }
|
||||||
|
let(:password_str) { 's3cr3t' }
|
||||||
|
|
||||||
|
context 'with dummy provider' do
|
||||||
|
let(:auth) {
|
||||||
|
{
|
||||||
|
'provider': 'dummy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it 'should return true' do
|
||||||
|
expect(subject).to receive(:authenticate).with(auth, username_str, password_str).and_return(true)
|
||||||
|
|
||||||
|
subject.authenticate(auth, username_str, password_str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with ldap provider' do
|
||||||
|
let(:host) { 'ldap.example.com' }
|
||||||
|
let(:base) { 'ou=user,dc=test,dc=com' }
|
||||||
|
let(:user_object) { 'uid' }
|
||||||
|
let(:auth) {
|
||||||
|
{
|
||||||
|
'provider' => 'ldap',
|
||||||
|
ldap: {
|
||||||
|
'host' => host,
|
||||||
|
'base' => base,
|
||||||
|
'user_object' => user_object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:default_port) { 389 }
|
||||||
|
it 'should attempt ldap authentication' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base, username_str, password_str)
|
||||||
|
|
||||||
|
subject.authenticate(auth, username_str, password_str)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return true when authentication is successful' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base, username_str, password_str).and_return(true)
|
||||||
|
|
||||||
|
expect(subject.authenticate(auth, username_str, password_str)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return false when authentication fails' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base, username_str, password_str).and_return(false)
|
||||||
|
|
||||||
|
expect(subject.authenticate(auth, username_str, password_str)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an alternate port' do
|
||||||
|
let(:alternate_port) { 636 }
|
||||||
|
before(:each) do
|
||||||
|
auth[:ldap]['port'] = alternate_port
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should specify the alternate port when authenticating' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(alternate_port, host, user_object, base, username_str, password_str)
|
||||||
|
|
||||||
|
subject.authenticate(auth, username_str, password_str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with multiple search bases' do
|
||||||
|
let(:base) {
|
||||||
|
[
|
||||||
|
'ou=user,dc=test,dc=com',
|
||||||
|
'ou=service,ou=user,dc=test,dc=com'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
before(:each) do
|
||||||
|
auth[:ldap]['base'] = base
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should attempt to bind with each base' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str)
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str)
|
||||||
|
|
||||||
|
subject.authenticate(auth, username_str, password_str)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not search the second base when the first binds' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str).and_return(true)
|
||||||
|
expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str)
|
||||||
|
|
||||||
|
subject.authenticate(auth, username_str, password_str)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should search the second base when the first bind fails' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str).and_return(false)
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str)
|
||||||
|
|
||||||
|
subject.authenticate(auth, username_str, password_str)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return true when any bind succeeds' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str).and_return(false)
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str).and_return(true)
|
||||||
|
|
||||||
|
expect(subject.authenticate(auth, username_str, password_str)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return false when all bind attempts fail' do
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[0], username_str, password_str).and_return(false)
|
||||||
|
expect(subject).to receive(:authenticate_ldap).with(default_port, host, user_object, base[1], username_str, password_str).and_return(false)
|
||||||
|
|
||||||
|
expect(subject.authenticate(auth, username_str, password_str)).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with unknown provider' do
|
||||||
|
let(:auth) {
|
||||||
|
{
|
||||||
|
'provider': 'mystery'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it 'should return false' do
|
||||||
|
expect(subject).to receive(:authenticate).with(auth, username_str, password_str).and_return(false)
|
||||||
|
subject.authenticate(auth, username_str, password_str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#authenticate_ldap' do
|
||||||
|
let(:port) { 389 }
|
||||||
|
let(:host) { 'ldap.example.com' }
|
||||||
|
let(:user_object) { 'uid' }
|
||||||
|
let(:base) { 'ou=users,dc=example,dc=com' }
|
||||||
|
let(:username_str) { 'admin' }
|
||||||
|
let(:password_str) { 's3cr3t' }
|
||||||
|
let(:ldap) { double('ldap') }
|
||||||
|
it 'should create a new ldap connection' do
|
||||||
|
allow(ldap).to receive(:bind)
|
||||||
|
expect(Net::LDAP).to receive(:new).with(
|
||||||
|
:host => host,
|
||||||
|
:port => port,
|
||||||
|
:encryption => {
|
||||||
|
:method => :start_tls,
|
||||||
|
:tls_options => { :ssl_version => 'TLSv1' }
|
||||||
|
},
|
||||||
|
:base => base,
|
||||||
|
:auth => {
|
||||||
|
:method => :simple,
|
||||||
|
:username => "#{user_object}=#{username_str},#{base}",
|
||||||
|
:password => password_str
|
||||||
|
}
|
||||||
|
).and_return(ldap)
|
||||||
|
|
||||||
|
subject.authenticate_ldap(port, host, user_object, base, username_str, password_str)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return true when a bind is successful' do
|
||||||
|
expect(Net::LDAP).to receive(:new).and_return(ldap)
|
||||||
|
expect(ldap).to receive(:bind).and_return(true)
|
||||||
|
|
||||||
|
expect(subject.authenticate_ldap(port, host, user_object, base, username_str, password_str)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return false when a bind fails' do
|
||||||
|
expect(Net::LDAP).to receive(:new).and_return(ldap)
|
||||||
|
expect(ldap).to receive(:bind).and_return(false)
|
||||||
|
|
||||||
|
expect(subject.authenticate_ldap(port, host, user_object, base, username_str, password_str)).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,19 @@ EOT
|
||||||
subject._check_pending_vm(vm, pool, timeout, provider)
|
subject._check_pending_vm(vm, pool, timeout, provider)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a locked vm mutex' do
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return' do
|
||||||
|
expect(subject).to receive(:vm_mutex).and_return(mutex)
|
||||||
|
|
||||||
|
expect(subject._check_pending_vm(vm, pool, timeout, provider)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#remove_nonexistent_vm' do
|
describe '#remove_nonexistent_vm' do
|
||||||
|
|
@ -404,6 +417,19 @@ EOT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a locked vm mutex' do
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return' do
|
||||||
|
expect(subject).to receive(:vm_mutex).and_return(mutex)
|
||||||
|
|
||||||
|
expect(subject._check_ready_vm(vm, pool, ttl, provider)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#check_running_vm' do
|
describe '#check_running_vm' do
|
||||||
|
|
@ -479,6 +505,19 @@ EOT
|
||||||
expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
|
expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a locked vm mutex' do
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return' do
|
||||||
|
expect(subject).to receive(:vm_mutex).and_return(mutex)
|
||||||
|
|
||||||
|
expect(subject._check_running_vm(vm, pool, timeout, provider)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#move_vm_queue' do
|
describe '#move_vm_queue' do
|
||||||
|
|
@ -732,6 +771,19 @@ EOT
|
||||||
expect{ subject._destroy_vm(vm,pool,provider) }.to raise_error(/MockError/)
|
expect{ subject._destroy_vm(vm,pool,provider) }.to raise_error(/MockError/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the VM mutex is locked' do
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return' do
|
||||||
|
expect(subject).to receive(:vm_mutex).with(vm).and_return(mutex)
|
||||||
|
|
||||||
|
expect(subject._destroy_vm(vm,pool,provider)).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#create_vm_disk' do
|
describe '#create_vm_disk' do
|
||||||
|
|
@ -1501,6 +1553,421 @@ EOT
|
||||||
subject.migrate_vm(vm, pool, provider)
|
subject.migrate_vm(vm, pool, provider)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a locked vm mutex' do
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return' do
|
||||||
|
expect(subject).to receive(:vm_mutex).and_return(mutex)
|
||||||
|
|
||||||
|
expect(subject.migrate_vm(vm, pool, provider)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#vm_mutex' do
|
||||||
|
it 'should return a mutex' do
|
||||||
|
expect(subject.vm_mutex(vm)).to be_a(Mutex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return the same mutex when called twice' do
|
||||||
|
first = subject.vm_mutex(vm)
|
||||||
|
second = subject.vm_mutex(vm)
|
||||||
|
expect(first).to be(second)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'sync_pool_template' do
|
||||||
|
let(:old_template) { 'templates/old-template' }
|
||||||
|
let(:new_template) { 'templates/new-template' }
|
||||||
|
let(:config) { YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:pools:
|
||||||
|
- name: '#{pool}'
|
||||||
|
size: 1
|
||||||
|
template: old_template
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it 'returns when a template is not set in redis' do
|
||||||
|
expect(subject.sync_pool_template(config[:pools][0])).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns when a template is set and matches the configured template' do
|
||||||
|
redis.hset('vmpooler__config__template', pool, old_template)
|
||||||
|
|
||||||
|
subject.sync_pool_template(config[:pools][0])
|
||||||
|
|
||||||
|
expect(config[:pools][0]['template']).to eq(old_template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates a pool template when the redis provided value is different' do
|
||||||
|
redis.hset('vmpooler__config__template', pool, new_template)
|
||||||
|
|
||||||
|
subject.sync_pool_template(config[:pools][0])
|
||||||
|
|
||||||
|
expect(config[:pools][0]['template']).to eq(new_template)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'pool_mutex' do
|
||||||
|
it 'should return a mutex' do
|
||||||
|
expect(subject.pool_mutex(pool)).to be_a(Mutex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return the same mutex when called twice' do
|
||||||
|
first = subject.pool_mutex(pool)
|
||||||
|
second = subject.pool_mutex(pool)
|
||||||
|
expect(first).to be(second)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'update_pool_template' do
|
||||||
|
let(:current_template) { 'templates/pool_template' }
|
||||||
|
let(:new_template) { 'templates/new_pool_template' }
|
||||||
|
let(:config) {
|
||||||
|
YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:config: {}
|
||||||
|
:pools:
|
||||||
|
- name: #{pool}
|
||||||
|
template: "#{current_template}"
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let(:poolconfig) { config[:pools][0] }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(logger).to receive(:log)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should set the pool template to match the configured template' do
|
||||||
|
subject.update_pool_template(poolconfig, provider, new_template, current_template)
|
||||||
|
|
||||||
|
expect(poolconfig['template']).to eq(new_template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should log that the template is updated' do
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] template updated from #{current_template} to #{new_template}")
|
||||||
|
|
||||||
|
subject.update_pool_template(poolconfig, provider, new_template, current_template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should run drain_pool' do
|
||||||
|
expect(subject).to receive(:drain_pool).with(pool)
|
||||||
|
|
||||||
|
subject.update_pool_template(poolconfig, provider, new_template, current_template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should log that a template is being prepared' do
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] preparing pool template for deployment")
|
||||||
|
|
||||||
|
subject.update_pool_template(poolconfig, provider, new_template, current_template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should run prepare_template' do
|
||||||
|
expect(subject).to receive(:prepare_template).with(poolconfig, provider)
|
||||||
|
|
||||||
|
subject.update_pool_template(poolconfig, provider, new_template, current_template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should log that the pool is ready for use' do
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] is ready for use")
|
||||||
|
|
||||||
|
subject.update_pool_template(poolconfig, provider, new_template, current_template)
|
||||||
|
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 0 total value' do
|
||||||
|
let(:ready) { 0 }
|
||||||
|
let(:total) { 0 }
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.remove_excess_vms(config[:pools][0], provider, ready, total)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the mutex is locked' do
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
let(:ready) { 2 }
|
||||||
|
let(:total) { 3 }
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
expect(subject).to receive(:pool_mutex).with(pool).and_return(mutex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.remove_excess_vms(config[:pools][0], provider, ready, total)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a total size less than the pool size' do
|
||||||
|
let(:ready) { 1 }
|
||||||
|
let(:total) { 2 }
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.remove_excess_vms(config[:pools][0], provider, ready, total)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a total size greater than the pool size' do
|
||||||
|
let(:ready) { 4 }
|
||||||
|
let(:total) { 4 }
|
||||||
|
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, ready, total)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should remove excess pending vms' do
|
||||||
|
create_pending_vm(pool,'vm1')
|
||||||
|
create_pending_vm(pool,'vm2')
|
||||||
|
create_ready_vm(pool, 'vm3')
|
||||||
|
create_ready_vm(pool, 'vm4')
|
||||||
|
create_ready_vm(pool, 'vm5')
|
||||||
|
expect(subject).to receive(:move_vm_queue).exactly(3).times
|
||||||
|
|
||||||
|
subject.remove_excess_vms(config[:pools][0], provider, 3, 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
context 'when creating the template delta disks' do
|
||||||
|
before(:each) do
|
||||||
|
allow(redis).to receive(:hset)
|
||||||
|
allow(provider).to receive(:create_template_delta_disks)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'evaluate_template' do
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
let(:current_template) { 'templates/template1' }
|
||||||
|
let(:new_template) { 'templates/template2' }
|
||||||
|
let(:config) { YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:config:
|
||||||
|
task_limit: 5
|
||||||
|
:providers:
|
||||||
|
:mock:
|
||||||
|
:pools:
|
||||||
|
- name: '#{pool}'
|
||||||
|
size: 1
|
||||||
|
template: '#{current_template}'
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(redis).to receive(:hget)
|
||||||
|
expect(subject).to receive(:pool_mutex).with(pool).and_return(mutex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should retreive the prepared template' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(current_template)
|
||||||
|
|
||||||
|
subject.evaluate_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should retrieve the redis configured template' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__template', pool).and_return(new_template)
|
||||||
|
|
||||||
|
subject.evaluate_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the mutex is locked' do
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return' do
|
||||||
|
expect(subject.evaluate_template(config[:pools][0], provider)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when prepared template is nil' do
|
||||||
|
before(:each) do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should prepare the template' do
|
||||||
|
expect(subject).to receive(:prepare_template).with(config[:pools][0], provider)
|
||||||
|
|
||||||
|
subject.evaluate_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a new template is requested' do
|
||||||
|
before(:each) do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(current_template)
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__template', pool).and_return(new_template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should update the template' do
|
||||||
|
expect(subject).to receive(:update_pool_template).with(config[:pools][0], provider, new_template, current_template)
|
||||||
|
|
||||||
|
subject.evaluate_template(config[:pools][0], provider)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'drain_pool' do
|
||||||
|
before(:each) do
|
||||||
|
allow(logger).to receive(:log)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with no vms' do
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.drain_pool(pool)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not log any messages' do
|
||||||
|
expect(logger).to_not receive(:log)
|
||||||
|
|
||||||
|
subject.drain_pool(pool)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not try to move any vms' do
|
||||||
|
expect(subject).to_not receive(:move_vm_queue)
|
||||||
|
|
||||||
|
subject.drain_pool(pool)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with ready vms' do
|
||||||
|
before(:each) do
|
||||||
|
create_ready_vm(pool, 'vm1')
|
||||||
|
create_ready_vm(pool, 'vm2')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes the ready instances' do
|
||||||
|
expect(subject).to receive(:move_vm_queue).twice
|
||||||
|
|
||||||
|
subject.drain_pool(pool)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs that ready instances are being removed' do
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] removing ready instances")
|
||||||
|
|
||||||
|
subject.drain_pool(pool)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with pending instances' do
|
||||||
|
before(:each) do
|
||||||
|
create_pending_vm(pool, 'vm1')
|
||||||
|
create_pending_vm(pool, 'vm2')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes the pending instances' do
|
||||||
|
expect(subject).to receive(:move_vm_queue).twice
|
||||||
|
|
||||||
|
subject.drain_pool(pool)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs that pending instances are being removed' do
|
||||||
|
expect(logger).to receive(:log).with('s', "[*] [#{pool}] removing pending instances")
|
||||||
|
|
||||||
|
subject.drain_pool(pool)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'update_pool_size' do
|
||||||
|
let(:newsize) { '3' }
|
||||||
|
let(:config) {
|
||||||
|
YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:pools:
|
||||||
|
- name: #{pool}
|
||||||
|
size: 2
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let(:poolconfig) { config[:pools][0] }
|
||||||
|
|
||||||
|
context 'with a locked mutex' do
|
||||||
|
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
expect(subject).to receive(:pool_mutex).with(pool).and_return(mutex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.update_pool_size(poolconfig)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should get the pool size configuration from redis' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool)
|
||||||
|
|
||||||
|
subject.update_pool_size(poolconfig)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return when poolsize is not set in redis' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool).and_return(nil)
|
||||||
|
|
||||||
|
expect(subject.update_pool_size(poolconfig)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return when no change in configuration is required' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool).and_return('2')
|
||||||
|
|
||||||
|
expect(subject.update_pool_size(poolconfig)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should update the pool size' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool).and_return(newsize)
|
||||||
|
|
||||||
|
subject.update_pool_size(poolconfig)
|
||||||
|
|
||||||
|
expect(poolconfig['size']).to eq(Integer(newsize))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#execute!" do
|
describe "#execute!" do
|
||||||
|
|
@ -1824,6 +2291,7 @@ 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__template__prepared').once
|
||||||
|
|
||||||
subject.execute!(maxloop,0)
|
subject.execute!(maxloop,0)
|
||||||
end
|
end
|
||||||
|
|
@ -1902,6 +2370,36 @@ EOT
|
||||||
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
|
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'with the pool_template_change wakeup option' do
|
||||||
|
let(:wakeup_option) {{
|
||||||
|
:pool_template_change => true,
|
||||||
|
:poolname => pool
|
||||||
|
}}
|
||||||
|
let(:new_template) { 'templates/newtemplate' }
|
||||||
|
let(:wakeup_period) { -1 } # A negative number forces the wakeup evaluation to always occur
|
||||||
|
|
||||||
|
context 'with a template configured' do
|
||||||
|
before(:each) do
|
||||||
|
redis.hset('vmpooler__config__template', pool, new_template)
|
||||||
|
allow(redis).to receive(:hget)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should check if a template is configured in redis' do
|
||||||
|
expect(subject).to receive(:time_passed?).with(:exit_by, Time).and_return(false, true)
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).once
|
||||||
|
|
||||||
|
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should sleep until a template change is detected' do
|
||||||
|
expect(subject).to receive(:sleep).exactly(3).times
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__template', pool).and_return(nil,nil,new_template)
|
||||||
|
|
||||||
|
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#check_pool" do
|
describe "#check_pool" do
|
||||||
|
|
@ -2221,21 +2719,25 @@ EOT
|
||||||
subject._check_pool(pool_object, provider)
|
subject._check_pool(pool_object, provider)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'captures #create_inventory errors correctly' do
|
it 'passes #create_inventory errors correctly' do
|
||||||
allow(subject).to receive(:create_inventory).and_raise(
|
allow(subject).to receive(:create_inventory).and_raise(
|
||||||
RuntimeError,'Mock Error'
|
RuntimeError,'Mock Error'
|
||||||
)
|
)
|
||||||
expect {
|
expect {
|
||||||
subject._check_pool(pool_object, provider)
|
subject._check_pool(pool_object, provider)
|
||||||
}.to_not raise_error(RuntimeError, /Mock Error/)
|
}.to raise_error(RuntimeError, /Mock Error/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should return early if an error occurs' do
|
it 'should not perform any other actions if an error occurs' do
|
||||||
allow(subject).to receive(:create_inventory).and_raise(
|
allow(subject).to receive(:create_inventory).and_raise(
|
||||||
RuntimeError,'Mock Error'
|
RuntimeError,'Mock Error'
|
||||||
)
|
)
|
||||||
expect(subject).to_not receive(:check_running_pool_vms)
|
|
||||||
|
expect {
|
||||||
subject._check_pool(pool_object, provider)
|
subject._check_pool(pool_object, provider)
|
||||||
|
}.to raise_error(RuntimeError, /Mock Error/)
|
||||||
|
|
||||||
|
expect(subject).to_not receive(:check_running_pool_vms)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should return that no actions were taken' do
|
it 'should return that no actions were taken' do
|
||||||
|
|
@ -2785,6 +3287,52 @@ 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
|
||||||
|
let(:poolsize) { 2 }
|
||||||
|
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
|
||||||
|
expect(subject).to_not receive(:clone_vm)
|
||||||
|
|
||||||
|
subject._check_pool(config[:pools][0],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,30 @@ EOT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'Given a template path that does not exist' do
|
context 'Given a template that starts with /' do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
config[:pools][0]['template'] = 'missing_Templates/pool1'
|
config[:pools][0]['template'] = '/bad_template'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should raise an error' do
|
it 'should raise an error' do
|
||||||
expect{ subject.create_vm(poolname, vmname) }.to raise_error(/specifies a template folder of .+ which does not exist/)
|
expect{ subject.create_vm(poolname, vmname) }.to raise_error(/did not specify a full path for the template/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Given a template that ends with /' do
|
||||||
|
before(:each) do
|
||||||
|
config[:pools][0]['template'] = 'bad_template/'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should raise an error' do
|
||||||
|
expect{ subject.create_vm(poolname, vmname) }.to raise_error(/did not specify a full path for the template/)
|
||||||
end
|
end
|
||||||
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 +339,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 +352,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)
|
||||||
|
|
@ -3318,57 +3331,6 @@ EOT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#find_vmdks' do
|
|
||||||
let(:datastorename) { 'datastore' }
|
|
||||||
let(:connection_options) {{
|
|
||||||
:serviceContent => {
|
|
||||||
:datacenters => [
|
|
||||||
{ :name => datacenter_name, :datastores => [datastorename] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
|
|
||||||
let(:collectMultiple_response) { {} }
|
|
||||||
|
|
||||||
before(:each) do
|
|
||||||
allow(connection.serviceContent.propertyCollector).to receive(:collectMultiple).and_return(collectMultiple_response)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'Searching all files for all VMs on a Datastore' do
|
|
||||||
# This is fairly fragile mocking
|
|
||||||
let(:collectMultiple_response) { {
|
|
||||||
'FakeVMObject1' => { 'layoutEx.file' =>
|
|
||||||
[
|
|
||||||
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 101, :name => "[#{datastorename}] mock1/mock1_0.vmdk"})
|
|
||||||
]},
|
|
||||||
vmname => { 'layoutEx.file' =>
|
|
||||||
[
|
|
||||||
# VMDKs which should match
|
|
||||||
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 1, :name => "[#{datastorename}] #{vmname}/#{vmname}_0.vmdk"}),
|
|
||||||
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 2, :name => "[#{datastorename}] #{vmname}/#{vmname}_1.vmdk"}),
|
|
||||||
# VMDKs which should not match
|
|
||||||
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 102, :name => "[otherdatastore] #{vmname}/#{vmname}_0.vmdk"}),
|
|
||||||
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 103, :name => "[otherdatastore] #{vmname}/#{vmname}.vmdk"}),
|
|
||||||
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 104, :name => "[otherdatastore] #{vmname}/#{vmname}_abc.vmdk"}),
|
|
||||||
]},
|
|
||||||
} }
|
|
||||||
|
|
||||||
it 'should return empty array if no VMDKs match the VM name' do
|
|
||||||
expect(subject.find_vmdks('missing_vm_name',datastorename,connection,datacenter_name)).to eq([])
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should return matching VMDKs for the VM' do
|
|
||||||
result = subject.find_vmdks(vmname,datastorename,connection,datacenter_name)
|
|
||||||
expect(result).to_not be_nil
|
|
||||||
expect(result.count).to eq(2)
|
|
||||||
# The keys for each VMDK should be less that 100 as per the mocks
|
|
||||||
result.each do |fileinfo|
|
|
||||||
expect(fileinfo.key).to be < 100
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#get_base_vm_container_from' do
|
describe '#get_base_vm_container_from' do
|
||||||
it 'should return a recursive view of type VirtualMachine' do
|
it 'should return a recursive view of type VirtualMachine' do
|
||||||
result = subject.get_base_vm_container_from(connection)
|
result = subject.get_base_vm_container_from(connection)
|
||||||
|
|
@ -3461,5 +3423,71 @@ 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 'valid_template_path?' do
|
||||||
|
|
||||||
|
it 'should return true with a valid template path' do
|
||||||
|
expect(subject.valid_template_path?('test/template')).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return false when no / is found' do
|
||||||
|
expect(subject.valid_template_path?('testtemplate')).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return false when template path begins with /' do
|
||||||
|
expect(subject.valid_template_path?('/testtemplate')).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return false when template path ends with /' do
|
||||||
|
expect(subject.valid_template_path?('testtemplate/')).to eq(false)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
#
|
#
|
||||||
# - insecure
|
# - insecure
|
||||||
# Whether to ignore any HTTPS negotiation errors (e.g. untrusted self-signed certificates)
|
# Whether to ignore any HTTPS negotiation errors (e.g. untrusted self-signed certificates)
|
||||||
# (optional: default true)
|
# (optional: default false)
|
||||||
#
|
#
|
||||||
# - datacenter
|
# - datacenter
|
||||||
# The datacenter within vCenter to manage VMs. This can be overridden in the pool configuration
|
# The datacenter within vCenter to manage VMs. This can be overridden in the pool configuration
|
||||||
|
|
@ -443,6 +443,11 @@
|
||||||
# The value represents a percentage and applies to both memory and CPU
|
# The value represents a percentage and applies to both memory and CPU
|
||||||
# (optional; default: 90)
|
# (optional; default: 90)
|
||||||
#
|
#
|
||||||
|
# - experimental_features (Only affects API config endpoints)
|
||||||
|
# Enable experimental API capabilities such as changing pool template and size without application restart
|
||||||
|
# Expects a boolean value
|
||||||
|
# (optional; default: false)
|
||||||
|
#
|
||||||
# Example:
|
# Example:
|
||||||
|
|
||||||
:config:
|
:config:
|
||||||
|
|
@ -458,6 +463,7 @@
|
||||||
- 'project'
|
- 'project'
|
||||||
domain: 'company.com'
|
domain: 'company.com'
|
||||||
prefix: 'poolvm-'
|
prefix: 'poolvm-'
|
||||||
|
experimental_features: true
|
||||||
|
|
||||||
# :pools:
|
# :pools:
|
||||||
#
|
#
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue