mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 01:58:41 -05:00
(POOLER-107) Add configuration API endpoint
This commit adds a configuration endpoint to the vmpooler API. Pool size, and pool template, can be adjusted for pools that are configured at vmpooler application start time. Pool template changes trigger a pool refresh, and the new template has delta disks created automatically by vmpooler. Additionally, the capability to create template delta disks is added to the vsphere provider, and this is implemented to ensure that templates have delta disks created at application start time. The mechanism used to find template VM objects is simplified to make the flow of logic easier to understand. As an additional benefit, performance of this lookup is improved by using FindByInventoryPath. 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:
parent
e781ed258b
commit
9bb4df7d8e
11 changed files with 1393 additions and 46 deletions
250
spec/integration/api/v1/config_spec.rb
Normal file
250
spec/integration/api/v1/config_spec.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue