(POOLER-107) Add configuration API endpoint

This commit adds a configuration endpoint to the vmpooler API. Pool
size, and pool template, can be adjusted for pools that are configured
at vmpooler application start time. Pool template changes trigger a pool
refresh, and the new template has delta disks created automatically by
vmpooler.

Additionally, the capability to create template delta disks is added to
the vsphere provider, and this is implemented to ensure that templates
have delta disks created at application start time.

The mechanism used to find template VM objects is simplified to make the flow of logic easier to understand. As an additional benefit, performance of this lookup is improved by using FindByInventoryPath.

A table of contents is added to API.md to ease navigation. Without this change API.md has no table of contents and is difficult to navigate.

Add mutex object for managing pool configuration updates

This commit adds a mutex object for ensuring that pool configuration changes are synchronized across multiple running threads, removing the possibility of two threads attempting to update something at once, without relying on redis data. Without this change this is managed crudely by specifying in redis that a configuration update is taking place. This redis data is left so the REPOPULATE section of _check_pool can still identify when a configuration change is in progress, and prevent a pool from repopulating at that time.

Add wake up event for pool template changes

This commit adds a wake up event to detect pool template changes.
Additionally, GET /config has a template_ready section added to the
output for each pool, which makes clear when a pool is ready to populate
itself.
This commit is contained in:
kirby@puppetlabs.com 2018-05-11 13:42:21 -07:00
parent e781ed258b
commit 9bb4df7d8e
11 changed files with 1393 additions and 46 deletions

View file

@ -0,0 +1,250 @@
require 'spec_helper'
require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
describe Vmpooler::API::V1 do
include Rack::Test::Methods
def app()
Vmpooler::API
end
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

View file

@ -168,4 +168,74 @@ describe Vmpooler::API::Helpers do
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
end

View file

@ -1503,6 +1503,396 @@ EOT
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
describe "#execute!" do
let(:config) {
YAML.load(<<-EOT
@ -1824,6 +2214,7 @@ EOT
it 'should run startup tasks only once' do
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__template__prepared').once
subject.execute!(maxloop,0)
end
@ -1902,8 +2293,38 @@ EOT
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
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
describe "#check_pool" do
let(:threads) {{}}
let(:provider_name) { 'mock_provider' }
@ -2785,6 +3206,52 @@ EOT
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
it 'increments metrics for ready queue' do
create_ready_vm(pool,'vm1')

View file

@ -283,6 +283,7 @@ EOT
let(:clone_vm_task) { mock_RbVmomi_VIM_Task() }
let(:new_vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
let(:new_template_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
@ -305,19 +306,30 @@ EOT
end
end
context 'Given a template path that does not exist' do
context 'Given a template that starts with /' do
before(:each) do
config[:pools][0]['template'] = 'missing_Templates/pool1'
config[:pools][0]['template'] = '/bad_template'
end
it 'should raise an error' do
expect{ subject.create_vm(poolname, vmname) }.to raise_error(/specifies a template folder of .+ which does not exist/)
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
context 'Given a template VM that does not exist' do
before(:each) do
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
it 'should raise an error' do
@ -327,7 +339,8 @@ EOT
context 'Given a successful creation' 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(clone_vm_task).to receive(:wait_for_completion).and_return(new_vm_object)
end
@ -339,7 +352,7 @@ EOT
end
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)
.with(create_vm_spec(vmname,'pool1','datastore0'))
.and_return(clone_vm_task)
@ -3461,5 +3474,71 @@ EOT
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