From fb80d989c871a07fa6209ec3316cd7f0fcfc861a Mon Sep 17 00:00:00 2001 From: Jake Spain Date: Thu, 23 Feb 2023 10:59:45 -0500 Subject: [PATCH] Removed api/v1 spec tests --- spec/integration/api/v1/config_spec.rb | 402 ------------------ spec/integration/api/v1/ondemandvm_spec.rb | 387 ----------------- spec/integration/api/v1/poolreset.rb | 120 ------ spec/integration/api/v1/status_spec.rb | 247 ----------- spec/integration/api/v1/token_spec.rb | 210 ---------- spec/integration/api/v1/vm_hostname_spec.rb | 352 ---------------- spec/integration/api/v1/vm_spec.rb | 435 -------------------- spec/integration/api/v1/vm_template_spec.rb | 327 --------------- spec/integration/api/v2/vm_spec.rb | 22 + 9 files changed, 22 insertions(+), 2480 deletions(-) delete mode 100644 spec/integration/api/v1/config_spec.rb delete mode 100644 spec/integration/api/v1/ondemandvm_spec.rb delete mode 100644 spec/integration/api/v1/poolreset.rb delete mode 100644 spec/integration/api/v1/status_spec.rb delete mode 100644 spec/integration/api/v1/token_spec.rb delete mode 100644 spec/integration/api/v1/vm_hostname_spec.rb delete mode 100644 spec/integration/api/v1/vm_spec.rb delete mode 100644 spec/integration/api/v1/vm_template_spec.rb diff --git a/spec/integration/api/v1/config_spec.rb b/spec/integration/api/v1/config_spec.rb deleted file mode 100644 index 62968bd..0000000 --- a/spec/integration/api/v1/config_spec.rb +++ /dev/null @@ -1,402 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -describe Vmpooler::API::V1 do - include Rack::Test::Methods - - def app() - Vmpooler::API - end - - # Added to ensure no leakage in rack state from previous tests. - # Removes all routes, filters, middleware and extension hooks from the current class - # https://rubydoc.info/gems/sinatra/Sinatra/Base#reset!-class_method - before(:each) do - app.reset! - end - - let(:config) { - { - config: { - 'site_name' => 'test pooler', - 'vm_lifetime_auth' => 2, - 'experimental_features' => true - }, - pools: [ - {'name' => 'pool1', 'size' => 5, 'template' => 'templates/pool1', 'clone_target' => 'default_cluster'}, - {'name' => 'pool2', 'size' => 10} - ], - pools_at_startup: [ - {'name' => 'pool1', 'size' => 5, 'template' => 'templates/pool1', 'clone_target' => 'default_cluster'}, - {'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::Metrics::DummyStatsd.new } - - let(:current_time) { Time.now } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, metrics, nil) - app.settings.set :config, auth: false - create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time) - end - - describe 'DELETE /config/pooltemplate/:pool' do - it 'resets a pool template' do - post "#{prefix}/config/pooltemplate", '{"pool1":"templates/new_template"}' - delete "#{prefix}/config/pooltemplate/pool1" - expect_json(ok = true, http = 201) - - expected = { - ok: true, - template_before_reset: 'templates/new_template', - template_before_overrides: 'templates/pool1' - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'succeeds when the pool has not been overridden' do - delete "#{prefix}/config/pooltemplate/pool1" - expect_json(ok = true, http = 200) - end - - it 'fails on nonexistent pools' do - delete "#{prefix}/config/pooltemplate/poolpoolpool" - expect_json(ok = false, http = 404) - end - - context 'with experimental features disabled' do - before(:each) do - config[:config]['experimental_features'] = false - end - - it 'should return 405' do - delete "#{prefix}/config/pooltemplate/pool1" - 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/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 'DELETE /config/poolsize' do - it 'resets a pool size' do - post "#{prefix}/config/poolsize", '{"pool1":"2"}' - delete "#{prefix}/config/poolsize/pool1" - expect_json(ok = true, http = 201) - - expected = { - ok: true, - pool_size_before_reset: 2, - pool_size_before_overrides: 5 - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'fails when a specified pool does not exist' do - delete "#{prefix}/config/poolsize/pool10" - expect_json(ok = false, http = 404) - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'succeeds when a pool has not been overridden' do - delete "#{prefix}/config/poolsize/pool1" - expect_json(ok = true, http = 200) - expected = { - ok: true, - pool_size_before_reset: 5, - pool_size_before_overrides: 5 - } - - 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 - delete "#{prefix}/config/poolsize/pool1" - 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, - not_configured: ['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, - not_configured: ['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, - not_configured: ['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 'POST /config/clonetarget' do - it 'changes the clone target' do - post "#{prefix}/config/clonetarget", '{"pool1":"cluster1"}' - 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/clonetarget", '{"pool1":"cluster1","pool2":"cluster2"}' - 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/clonetarget", '{"pool10":"cluster1"}' - 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/clonetarget", '{"pool1":"default_cluster"}' - 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/clonetarget", '{"pool1":"default_cluster","pool2":"cluster2"}' - expect_json(ok = true, http = 201) - - expected = { ok: true } - - 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/clonetarget", '{"pool1":"cluster1"}' - 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 diff --git a/spec/integration/api/v1/ondemandvm_spec.rb b/spec/integration/api/v1/ondemandvm_spec.rb deleted file mode 100644 index a361f9f..0000000 --- a/spec/integration/api/v1/ondemandvm_spec.rb +++ /dev/null @@ -1,387 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -describe Vmpooler::API::V1 do - include Rack::Test::Methods - - def app() - Vmpooler::API - end - # Added to ensure no leakage in rack state from previous tests. - # Removes all routes, filters, middleware and extension hooks from the current class - # https://rubydoc.info/gems/sinatra/Sinatra/Base#reset!-class_method - before(:each) do - app.reset! - end - - describe '/ondemandvm' do - let(:prefix) { '/api/v1' } - let(:metrics) { Vmpooler::Metrics::DummyStatsd.new } - let(:config) { - { - config: { - 'site_name' => 'test pooler', - 'vm_lifetime_auth' => 2, - 'max_ondemand_instances_per_request' => 50, - 'backend_weight' => { - 'compute1' => 5, - 'compute2' => 0 - } - }, - pools: [ - {'name' => 'pool1', 'size' => 0, 'clone_target' => 'compute1'}, - {'name' => 'pool2', 'size' => 0, 'clone_target' => 'compute2'}, - {'name' => 'pool3', 'size' => 0, 'clone_target' => 'compute1'} - ], - alias: { - 'poolone' => ['pool1'], - 'pool2' => ['pool1'] - }, - pool_names: [ 'pool1', 'pool2', 'pool3', 'poolone' ] - } - } - let(:current_time) { Time.now } - let(:vmname) { 'abcdefghijkl' } - let(:checkoutlock) { Mutex.new } - let(:uuid) { SecureRandom.uuid } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, metrics, nil) - app.settings.set :config, auth: false - app.settings.set :checkoutlock, checkoutlock - create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time) - config[:pools].each do |pool| - redis.sadd('vmpooler__pools', pool['name']) - end - end - - describe 'POST /ondemandvm' do - - context 'with a configured pool' do - - context 'with no request_id provided in payload' do - before(:each) do - expect(SecureRandom).to receive(:uuid).and_return(uuid) - end - - it 'generates a request_id when none is provided' do - post "#{prefix}/ondemandvm", '{"pool1":"1"}' - expect_json(true, 201) - - expected = { - "ok": true, - "request_id": uuid - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'uses a configured platform to fulfill a ondemand request' do - post "#{prefix}/ondemandvm", '{"poolone":"1"}' - expect_json(true, 201) - expected = { - "ok": true, - "request_id": uuid - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'creates a provisioning request in redis' do - expect(redis).to receive(:zadd).with('vmpooler__provisioning__request', Integer, uuid).and_return(1) - post "#{prefix}/ondemandvm", '{"poolone":"1"}' - end - - it 'sets a platform string in redis for the request to indicate selected platforms' do - expect(redis).to receive(:hset).with("vmpooler__odrequest__#{uuid}", 'requested', 'poolone:pool1:1') - post "#{prefix}/ondemandvm", '{"poolone":"1"}' - end - - context 'with a backend of 0 weight' do - before(:each) do - config[:config]['backend_weight']['compute1'] = 0 - end - - it 'sets the platform string in redis for the request to indicate the selected platforms' do - expect(redis).to receive(:hset).with("vmpooler__odrequest__#{uuid}", 'requested', 'pool1:pool1:1') - post "#{prefix}/ondemandvm", '{"pool1":"1"}' - end - end - - it 'sets the platform string in redis for the request to indicate the selected platforms using weight' do - expect(redis).to receive(:hset).with("vmpooler__odrequest__#{uuid}", 'requested', 'pool2:pool1:1') - post "#{prefix}/ondemandvm", '{"pool2":"1"}' - end - - context 'with domain set in the config' do - let(:domain) { 'example.com' } - before(:each) do - config[:config]['domain'] = domain - end - - it 'should include domain in the return reply' do - post "#{prefix}/ondemandvm", '{"poolone":"1"}' - expect_json(true, 201) - expected = { - "ok": true, - "request_id": uuid, - "domain": domain - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - end - - context 'with a resource request that exceeds the specified limit' do - let(:max_instances) { 50 } - before(:each) do - config[:config]['max_ondemand_instances_per_request'] = max_instances - end - - it 'should reject the request with a message' do - post "#{prefix}/ondemandvm", '{"pool1":"51"}' - expect_json(false, 403) - expected = { - "ok": false, - "message": "requested amount of instances exceeds the maximum #{max_instances}" - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - - context 'with request_id provided in the payload' do - it 'uses the given request_id when provided' do - post "#{prefix}/ondemandvm", '{"pool1":"1","request_id":"1234"}' - expect_json(true, 201) - - expected = { - "ok": true, - "request_id": "1234" - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'returns 409 conflict error when the request_id has been used' do - post "#{prefix}/ondemandvm", '{"pool1":"1","request_id":"1234"}' - post "#{prefix}/ondemandvm", '{"pool1":"1","request_id":"1234"}' - expect_json(false, 409) - - expected = { - "ok": false, - "request_id": "1234", - "message": "request_id '1234' has already been created" - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - - context 'with auth configured' do - - it 'sets the token and user' do - app.settings.set :config, auth: true - expect(SecureRandom).to receive(:uuid).and_return(uuid) - allow(redis).to receive(:hset) - expect(redis).to receive(:hset).with("vmpooler__odrequest__#{uuid}", 'token:token', 'abcdefghijklmnopqrstuvwxyz012345') - expect(redis).to receive(:hset).with("vmpooler__odrequest__#{uuid}", 'token:user', 'jdoe') - post "#{prefix}/ondemandvm", '{"pool1":"1"}', { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - end - end - end - - context 'with a pool that is not configured' do - let(:badpool) { 'pool4' } - it 'returns the bad template' do - post "#{prefix}/ondemandvm", '{"pool4":"1"}' - expect_json(false, 404) - - expected = { - "ok": false, - "bad_templates": [ badpool ] - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - - it 'returns 400 and a message when JSON is invalid' do - post "#{prefix}/ondemandvm", '{"pool1":"1}' - expect_json(false, 400) - expected = { - "ok": false, - "message": "JSON payload could not be parsed" - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - - describe 'GET /ondemandvm' do - it 'returns 404 with message when request is not found' do - get "#{prefix}/ondemandvm/#{uuid}" - expect_json(false, 404) - expected = { - "ok": false, - "message": "no request found for request_id '#{uuid}'" - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - context 'when the request is found' do - let(:score) { current_time } - let(:platforms_string) { 'pool1:pool1:1' } - before(:each) do - create_ondemand_request_for_test(uuid, score, platforms_string, redis) - end - - it 'returns 202 while the request is waiting' do - get "#{prefix}/ondemandvm/#{uuid}" - expect_json(true, 202) - expected = { - "ok": true, - "request_id": uuid, - "ready": false, - "pool1": { - "ready": "0", - "pending": "1" - } - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - context 'with ready instances' do - before(:each) do - create_ondemand_vm(vmname, uuid, 'pool1', 'pool1', redis) - set_ondemand_request_status(uuid, 'ready', redis) - end - - it 'returns 200 with hostnames when the request is ready' do - get "#{prefix}/ondemandvm/#{uuid}" - expect_json(true, 200) - expected = { - "ok": true, - "request_id": uuid, - "ready": true, - "pool1": { - "hostname": [ - vmname - ] - } - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - context 'with domain set' do - let(:domain) { 'example.com' } - before(:each) do - config[:config]['domain'] = domain - end - - it 'should include the domain in the result' do - get "#{prefix}/ondemandvm/#{uuid}" - expected = { - "ok": true, - "request_id": uuid, - "ready": true, - "pool1": { - "hostname": [ - vmname - ] - }, - "domain": domain - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - end - - context 'with a deleted request' do - before(:each) do - set_ondemand_request_status(uuid, 'deleted', redis) - end - - it 'returns a message that the request has been deleted' do - get "#{prefix}/ondemandvm/#{uuid}" - expect_json(true, 200) - expected = { - "ok": true, - "request_id": uuid, - "ready": false, - "message": "The request has been deleted" - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - - context 'with a failed request' do - let(:ondemand_request_ttl) { 5 } - before(:each) do - config[:config]['ondemand_request_ttl'] = ondemand_request_ttl - set_ondemand_request_status(uuid, 'failed', redis) - end - - it 'returns a message that the request has failed' do - get "#{prefix}/ondemandvm/#{uuid}" - expect_json(true, 200) - expected = { - "ok": true, - "request_id": uuid, - "ready": false, - "message": "The request failed to provision instances within the configured ondemand_request_ttl '#{ondemand_request_ttl}'" - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - end - end - - describe 'DELETE /ondemandvm' do - let(:expiration) { 129_600_0 } - it 'returns 404 with message when request is not found' do - delete "#{prefix}/ondemandvm/#{uuid}" - expect_json(false, 404) - expected = { - "ok": false, - "message": "no request found for request_id '#{uuid}'" - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - context 'when the request is found' do - let(:platforms_string) { 'pool1:pool1:1' } - let(:score) { current_time.to_i } - before(:each) do - create_ondemand_request_for_test(uuid, score, platforms_string, redis) - end - - it 'returns 200 for a deleted request' do - delete "#{prefix}/ondemandvm/#{uuid}" - expect_json(true, 200) - expected = { 'ok': true } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'marks the request hash for expiration in two weeks' do - expect(redis).to receive(:expire).with("vmpooler__odrequest__#{uuid}", expiration) - delete "#{prefix}/ondemandvm/#{uuid}" - end - - context 'with running instances' do - let(:pool) { 'pool1' } - let(:pool_alias) { pool } - before(:each) do - create_ondemand_vm(vmname, uuid, pool, pool_alias, redis) - end - - it 'moves allocated instances to the completed queue' do - expect(redis).to receive(:smove).with("vmpooler__running__#{pool}", "vmpooler__completed__#{pool}", vmname) - delete "#{prefix}/ondemandvm/#{uuid}" - end - - it 'deletes the set tracking instances allocated for the request' do - expect(redis).to receive(:del).with("vmpooler__#{uuid}__#{pool_alias}__#{pool}") - delete "#{prefix}/ondemandvm/#{uuid}" - end - end - end - end - end -end diff --git a/spec/integration/api/v1/poolreset.rb b/spec/integration/api/v1/poolreset.rb deleted file mode 100644 index dfb7f18..0000000 --- a/spec/integration/api/v1/poolreset.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -describe Vmpooler::API::V1 do - include Rack::Test::Methods - - def app() - Vmpooler::API - end - - after(:each) do - Vmpooler::API.reset! - end - - let(:config) { - { - config: { - 'site_name' => 'test pooler', - 'vm_lifetime_auth' => 2, - 'experimental_features' => true - }, - pools: [ - {'name' => 'pool1', 'size' => 5, 'template' => 'templates/pool1', 'clone_target' => 'default_cluster'}, - {'name' => 'pool2', 'size' => 10} - ], - statsd: { 'prefix' => 'stats_prefix'}, - alias: { 'poolone' => 'pool1' }, - pool_names: [ 'pool1', 'pool2', 'poolone' ] - } - } - - describe '/poolreset' do - let(:prefix) { '/api/v1' } - let(:metrics) { Vmpooler::Metrics::DummyStatsd.new } - - let(:current_time) { Time.now } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, metrics, nil) - app.settings.set :config, auth: false - create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time) - end - - describe 'POST /poolreset' do - it 'refreshes ready and pending instances from a pool' do - post "#{prefix}/poolreset", '{"pool1":"1"}' - 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}/poolreset", '{"poolpoolpool":"1"}' - expect_json(ok = false, http = 400) - end - - it 'resets multiple pools' do - post "#{prefix}/poolreset", '{"pool1":"1","pool2":"1"}' - 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}/poolreset", '{"pool1":"1","pool3":"1"}' - expect_json(ok = false, http = 400) - - expected = { - ok: false, - bad_pools: ['pool3'] - } - - 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}/poolreset", '{"pool1":"1"}' - expect_json(ok = false, http = 405) - - expected = { ok: false } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - - it 'should return 400 for invalid json' do - post "#{prefix}/poolreset", '{"pool1":"1}' - expect_json(ok = false, http = 400) - - expected = { ok: false } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'should return 400 with a bad pool name' do - post "#{prefix}/poolreset", '{"pool11":"1"}' - expect_json(ok = false, http = 400) - - expected = { ok: false } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'should return 404 when there is no payload' do - post "#{prefix}/poolreset" - expect_json(ok = false, http = 404) - - expected = { ok: false } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - end - end -end diff --git a/spec/integration/api/v1/status_spec.rb b/spec/integration/api/v1/status_spec.rb deleted file mode 100644 index 76e65df..0000000 --- a/spec/integration/api/v1/status_spec.rb +++ /dev/null @@ -1,247 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -def has_set_tag?(vm, tag, value) - value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}") -end - -describe Vmpooler::API::V1 do - include Rack::Test::Methods - - def app() - Vmpooler::API - end - - # Added to ensure no leakage in rack state from previous tests. - # Removes all routes, filters, middleware and extension hooks from the current class - # https://rubydoc.info/gems/sinatra/Sinatra/Base#reset!-class_method - before(:each) do - app.reset! - end - - describe 'status and metrics endpoints' do - let(:prefix) { '/api/v1' } - - let(:config) { - { - config: { - 'site_name' => 'test pooler', - 'vm_lifetime_auth' => 2, - }, - pools: [ - {'name' => 'pool1', 'size' => 5, 'alias' => ['poolone', 'poolun']}, - {'name' => 'pool2', 'size' => 10}, - {'name' => 'pool3', 'size' => 10, 'alias' => 'NotArray'} - ] - } - } - - let(:current_time) { Time.now } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, nil, nil) - app.settings.set :config, auth: false - create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time) - end - - describe 'GET /status' do - it 'returns the configured maximum size for each pool' do - get "#{prefix}/status/" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["pools"]["pool1"]["max"]).to be(5) - expect(result["pools"]["pool2"]["max"]).to be(10) - end - - it 'returns the number of ready vms for each pool' do - 3.times {|i| create_ready_vm("pool1", "vm-#{i}", redis) } - get "#{prefix}/status/" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["pools"]["pool1"]["ready"]).to be(3) - expect(result["pools"]["pool2"]["ready"]).to be(0) - end - - it 'returns the number of running vms for each pool' do - 3.times {|i| create_running_vm("pool1", "vm-#{i}", redis) } - 4.times {|i| create_running_vm("pool2", "vm-#{i}", redis) } - - get "#{prefix}/status/" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["pools"]["pool1"]["running"]).to be(3) - expect(result["pools"]["pool2"]["running"]).to be(4) - end - - it 'returns the number of pending vms for each pool' do - 3.times {|i| create_pending_vm("pool1", "vm-#{i}", redis) } - 4.times {|i| create_pending_vm("pool2", "vm-#{i}", redis) } - - get "#{prefix}/status/" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["pools"]["pool1"]["pending"]).to be(3) - expect(result["pools"]["pool2"]["pending"]).to be(4) - end - - it 'returns aliases if configured in the pool' do - get "#{prefix}/status/" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["pools"]["pool1"]["alias"]).to eq(['poolone', 'poolun']) - expect(result["pools"]["pool2"]["alias"]).to be(nil) - expect(result["pools"]["pool3"]["alias"]).to eq('NotArray') - end - - it '(for v1 backwards compatibility) lists any empty pools in the status section' do - get "#{prefix}/status/" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["status"]["empty"].sort).to eq(["pool1", "pool2", "pool3"]) - end - end - describe 'GET /status with view query parameter' do - it 'returns capacity when specified' do - get "#{prefix}/status?view=capacity" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["capacity"]).to_not be(nil) - expect(result["queue"]).to be(nil) - expect(result["clone"]).to be(nil) - expect(result["boot"]).to be(nil) - expect(result["pools"]).to be(nil) - expect(result["status"]).to_not be(nil) - end - it 'returns pools and queue when specified' do - get "#{prefix}/status?view=pools,queue" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["capacity"]).to be(nil) - expect(result["queue"]).to_not be(nil) - expect(result["clone"]).to be(nil) - expect(result["boot"]).to be(nil) - expect(result["pools"]).to_not be(nil) - expect(result["status"]).to_not be(nil) - end - it 'does nothing with invalid view names' do - get "#{prefix}/status?view=clone,boot,invalidThingToView" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["capacity"]).to be(nil) - expect(result["queue"]).to be(nil) - expect(result["clone"]).to_not be(nil) - expect(result["boot"]).to_not be(nil) - expect(result["pools"]).to be(nil) - expect(result["status"]).to_not be(nil) - end - it 'returns everything when view is not specified' do - get "#{prefix}/status" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["capacity"]).to_not be(nil) - expect(result["queue"]).to_not be(nil) - expect(result["clone"]).to_not be(nil) - expect(result["boot"]).to_not be(nil) - expect(result["pools"]).to_not be(nil) - expect(result["status"]).to_not be(nil) - end - it 'returns everything when view is alone' do - get "#{prefix}/status?view" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["capacity"]).to_not be(nil) - expect(result["queue"]).to_not be(nil) - expect(result["clone"]).to_not be(nil) - expect(result["boot"]).to_not be(nil) - expect(result["pools"]).to_not be(nil) - expect(result["status"]).to_not be(nil) - end - it 'returns status only when view is empty' do - get "#{prefix}/status?view=" - - # of course /status doesn't conform to the weird standard everything else uses... - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["capacity"]).to be(nil) - expect(result["queue"]).to be(nil) - expect(result["clone"]).to be(nil) - expect(result["boot"]).to be(nil) - expect(result["pools"]).to be(nil) - expect(result["status"]).to_not be(nil) - end - end - - describe 'GET /poolstat' do - it 'returns empty list when pool is not set' do - get "#{prefix}/poolstat" - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result == {}) - end - it 'returns empty list when pool is not found' do - get "#{prefix}/poolstat?pool=unknownpool" - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result == {}) - end - it 'returns one pool when requesting one with alias' do - get "#{prefix}/poolstat?pool=pool1" - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["pools"].size == 1) - expect(result["pools"]["pool1"]["ready"]).to eq(0) - expect(result["pools"]["pool1"]["max"]).to eq(5) - expect(result["pools"]["pool1"]["alias"]).to eq(['poolone', 'poolun']) - end - it 'returns one pool when requesting one without alias' do - get "#{prefix}/poolstat?pool=pool2" - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["pools"].size == 1) - expect(result["pools"]["pool2"]["ready"]).to eq(0) - expect(result["pools"]["pool2"]["max"]).to eq(10) - expect(result["pools"]["pool2"]["alias"]).to be(nil) - end - it 'returns multiple pools when requesting csv' do - get "#{prefix}/poolstat?pool=pool1,pool2" - expect(last_response.header['Content-Type']).to eq('application/json') - result = JSON.parse(last_response.body) - expect(result["pools"].size == 2) - end - end - - describe 'GET /totalrunning' do - it 'returns the number of running VMs' do - get "#{prefix}/totalrunning" - expect(last_response.header['Content-Type']).to eq('application/json') - 5.times {|i| create_running_vm("pool1", "vm-#{i}", redis, redis) } - 5.times {|i| create_running_vm("pool3", "vm-#{i}", redis, redis) } - result = JSON.parse(last_response.body) - expect(result["running"] == 10) - end - end - end -end diff --git a/spec/integration/api/v1/token_spec.rb b/spec/integration/api/v1/token_spec.rb deleted file mode 100644 index 72ab060..0000000 --- a/spec/integration/api/v1/token_spec.rb +++ /dev/null @@ -1,210 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -describe Vmpooler::API::V1 do - include Rack::Test::Methods - - def app() - Vmpooler::API - end - - # Added to ensure no leakage in rack state from previous tests. - # Removes all routes, filters, middleware and extension hooks from the current class - # https://rubydoc.info/gems/sinatra/Sinatra/Base#reset!-class_method - before(:each) do - app.reset! - end - - describe '/token' do - let(:prefix) { '/api/v1' } - let(:current_time) { Time.now } - let(:config) { { - config: {} - } } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, nil, nil) - end - - describe 'GET /token' do - context '(auth not configured)' do - let(:config) { { - config: {}, - auth: false - } } - - it 'returns a 404' do - get "#{prefix}/token" - expect_json(ok = false, http = 404) - end - end - - context '(auth configured)' do - let(:config) { - { - config: {}, - auth: { - 'provider' => 'dummy' - } - } - } - let(:username_str) { 'admin' } - let(:password_str) { 's3cr3t' } - - it 'returns a 401 if not authed' do - get "#{prefix}/token" - expect_json(ok = false, http = 401) - end - - it 'returns a list of tokens if authed' do - create_token "abc", "admin", current_time - - authorize 'admin', 's3cr3t' - get "#{prefix}/token" - expect_json(ok = true, http = 200) - - expect(JSON.parse(last_response.body)['abc']['created']).to eq(current_time.to_s) - end - end - end - - describe 'POST /token' do - context '(auth not configured)' do - let(:config) { { - config: {}, - auth: false - } } - - it 'returns a 404' do - post "#{prefix}/token" - expect_json(ok = false, http = 404) - end - end - - context '(auth configured)' do - let(:config) { - { - config: {}, - auth: { - 'provider' => 'dummy' - } - } - } - - it 'returns a 401 if not authed' do - post "#{prefix}/token" - expect_json(ok = false, http = 401) - end - - it 'returns a newly created token if authed' do - authorize 'admin', 's3cr3t' - post "#{prefix}/token" - expect_json(ok = true, http = 200) - - returned_token = JSON.parse(last_response.body)['token'] - expect(returned_token.length).to be(32) - expect(get_token_data(returned_token)['user']).to eq("admin") - end - end - end - end - - describe '/token/:token' do - let(:prefix) { '/api/v1' } - let(:current_time) { Time.now } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, nil, nil) - app.settings.set :config, config - app.settings.set :redis, redis - end - - def create_vm_for_token(token, pool, vm) - redis.sadd("vmpooler__running__#{pool}", vm) - redis.hset("vmpooler__vm__#{vm}", "token:token", token) - end - - describe 'GET /token/:token' do - context '(auth not configured)' do - let(:config) { { - config: {}, - auth: false - } } - - it 'returns a 404' do - get "#{prefix}/token/this" - expect_json(ok = false, http = 404) - end - end - - context '(auth configured)' do - let(:config) { { - config: {}, - auth: true, - pools: [ - {'name' => 'pool1', 'size' => 5} - ] - } } - - it 'returns a token' do - create_token "mytoken", "admin", current_time - create_vm_for_token "mytoken", "pool1", "vmhostname" - - get "#{prefix}/token/mytoken" - expect_json(ok = true, http = 200) - - expect(JSON.parse(last_response.body)['ok']).to eq(true) - expect(JSON.parse(last_response.body)['mytoken']['user']).to eq('admin') - expect(JSON.parse(last_response.body)['mytoken']['vms']['running']).to include('vmhostname') - end - end - end - - describe 'DELETE /token/:token' do - context '(auth not configured)' do - let(:config) { { - config: {}, - auth: false - } } - - it 'returns a 404' do - delete "#{prefix}/token/this" - expect_json(ok = false, http = 404) - end - end - - context '(auth configured)' do - let(:config) { - { - config: {}, - auth: { - 'provider' => 'dummy' - } - } - } - - it 'returns a 401 if not authed' do - delete "#{prefix}/token/this" - expect_json(ok = false, http = 401) - end - - it 'deletes a token if authed' do - create_token("mytoken", "admin", current_time) - authorize 'admin', 's3cr3t' - - delete "#{prefix}/token/mytoken" - expect_json(ok = true, http = 200) - end - - it 'fails if token does not exist' do - authorize 'admin', 's3cr3t' - - delete "#{prefix}/token/missingtoken" - expect_json(ok = false, http = 401) # TODO: should this be 404? - end - end - end - end -end diff --git a/spec/integration/api/v1/vm_hostname_spec.rb b/spec/integration/api/v1/vm_hostname_spec.rb deleted file mode 100644 index ff5f7a3..0000000 --- a/spec/integration/api/v1/vm_hostname_spec.rb +++ /dev/null @@ -1,352 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -def has_set_tag?(vm, tag, value) - value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}") -end - -describe Vmpooler::API::V1 do - include Rack::Test::Methods - - def app() - Vmpooler::API - end - - # Added to ensure no leakage in rack state from previous tests. - # Removes all routes, filters, middleware and extension hooks from the current class - # https://rubydoc.info/gems/sinatra/Sinatra/Base#reset!-class_method - before(:each) do - app.reset! - end - - describe '/vm/:hostname' do - let(:prefix) { '/api/v1' } - let(:metrics) { Vmpooler::Metrics::DummyStatsd.new } - - let(:config) { - { - config: { - 'site_name' => 'test pooler', - 'vm_lifetime_auth' => 2, - - }, - pools: [ - {'name' => 'pool1', 'size' => 5}, - {'name' => 'pool2', 'size' => 10} - ], - alias: { 'poolone' => 'pool1' }, - auth: false - } - } - - let(:current_time) { Time.now } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, metrics, nil) - create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time) - end - - describe 'PUT /vm/:hostname' do - it 'allows tags to be set' do - create_vm('testhost', redis) - put "#{prefix}/vm/testhost", '{"tags":{"tested_by":"rspec"}}' - expect_json(ok = true, http = 200) - - expect has_set_tag?('testhost', 'tested_by', 'rspec') - end - - it 'skips empty tags' do - create_vm('testhost', redis) - put "#{prefix}/vm/testhost", '{"tags":{"tested_by":""}}' - expect_json(ok = true, http = 200) - - expect !has_set_tag?('testhost', 'tested_by', '') - end - - it 'does not set tags if request body format is invalid' do - create_vm('testhost', redis) - put "#{prefix}/vm/testhost", '{"tags":{"tested"}}' - expect_json(ok = false, http = 400) - - expect !has_set_tag?('testhost', 'tested', '') - end - - context '(allowed_tags configured)' do - it 'fails if specified tag is not in allowed_tags array' do - app.settings.set :config, - { :config => { 'allowed_tags' => ['created_by', 'project', 'url'] } } - - create_vm('testhost', redis) - - put "#{prefix}/vm/testhost", '{"tags":{"created_by":"rspec","tested_by":"rspec"}}' - expect_json(ok = false, http = 400) - - expect !has_set_tag?('testhost', 'tested_by', 'rspec') - end - end - - context '(tagfilter configured)' do - before(:each) do - app.settings.set :config, tagfilter: { 'url' => '(.*)\/' } - end - - it 'correctly filters tags' do - create_vm('testhost', redis) - - put "#{prefix}/vm/testhost", '{"tags":{"url":"foo.com/something.html"}}' - expect_json(ok = true, http = 200) - - expect has_set_tag?('testhost', 'url', 'foo.com') - end - - it "doesn't eat tags not matching filter" do - create_vm('testhost', redis) - put "#{prefix}/vm/testhost", '{"tags":{"url":"foo.com"}}' - expect_json(ok = true, http = 200) - - expect has_set_tag?('testhost', 'url', 'foo.com') - end - end - - context '(auth not configured)' do - before(:each) do - app.settings.set :config, auth: false - end - - it 'allows VM lifetime to be modified without a token' do - create_vm('testhost', redis) - - put "#{prefix}/vm/testhost", '{"lifetime":"1"}' - expect_json(ok = true, http = 200) - - vm = fetch_vm('testhost') - expect(vm['lifetime'].to_i).to eq(1) - end - - it 'does not allow a lifetime to be 0' do - create_vm('testhost', redis) - - put "#{prefix}/vm/testhost", '{"lifetime":"0"}' - expect_json(ok = false, http = 400) - - vm = fetch_vm('testhost') - expect(vm['lifetime']).to be_nil - end - - it 'does not enforce a lifetime' do - create_vm('testhost', redis) - - put "#{prefix}/vm/testhost", '{"lifetime":"20000"}' - expect_json(ok = true, http = 200) - - vm = fetch_vm('testhost') - expect(vm['lifetime']).to eq("20000") - end - - it 'does not allow a lifetime to be initially past config max_lifetime_upper_limit' do - app.settings.set :config, - { :config => { 'max_lifetime_upper_limit' => 168 } } - create_vm('testhost', redis) - - put "#{prefix}/vm/testhost", '{"lifetime":"200"}' - expect_json(ok = false, http = 400) - - vm = fetch_vm('testhost') - expect(vm['lifetime']).to be_nil - end - -# it 'does not allow a lifetime to be extended past config 168' do -# app.settings.set :config, -# { :config => { 'max_lifetime_upper_limit' => 168 } } -# create_vm('testhost', redis) -# -# set_vm_data('testhost', "checkout", (Time.now - (69*60*60)), redis) -# puts redis.hget("vmpooler__vm__testhost", 'checkout') -# put "#{prefix}/vm/testhost", '{"lifetime":"100"}' -# expect_json(ok = false, http = 400) -# -# vm = fetch_vm('testhost') -# expect(vm['lifetime']).to be_nil -# end - end - - context '(auth configured)' do - before(:each) do - app.settings.set :config, auth: true - end - - it 'allows VM lifetime to be modified with a token' do - create_vm('testhost', redis) - - put "#{prefix}/vm/testhost", '{"lifetime":"1"}', { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = true, http = 200) - - vm = fetch_vm('testhost') - expect(vm['lifetime'].to_i).to eq(1) - end - - it 'does not allows VM lifetime to be modified without a token' do - create_vm('testhost', redis) - - put "#{prefix}/vm/testhost", '{"lifetime":"1"}' - expect_json(ok = false, http = 401) - end - end - end - - describe 'DELETE /vm/:hostname' do - context '(auth not configured)' do - it 'does not delete a non-existant VM' do - delete "#{prefix}/vm/testhost" - expect_json(ok = false, http = 404) - end - - it 'deletes an existing VM' do - create_running_vm('pool1', 'testhost', redis) - expect fetch_vm('testhost') - - delete "#{prefix}/vm/testhost" - expect_json(ok = true, http = 200) - expect !fetch_vm('testhost') - end - end - - context '(auth configured)' do - before(:each) do - app.settings.set :config, auth: true - end - - context '(checked-out without token)' do - it 'deletes a VM without supplying a token' do - create_running_vm('pool1', 'testhost', redis) - expect fetch_vm('testhost') - - delete "#{prefix}/vm/testhost" - expect_json(ok = true, http = 200) - expect !fetch_vm('testhost') - end - end - - context '(checked-out with token)' do - it 'fails to delete a VM without supplying a token' do - create_running_vm('pool1', 'testhost', redis, 'abcdefghijklmnopqrstuvwxyz012345') - expect fetch_vm('testhost') - - delete "#{prefix}/vm/testhost" - expect_json(ok = false, http = 401) - expect fetch_vm('testhost') - end - - it 'deletes a VM when token is supplied' do - create_running_vm('pool1', 'testhost', redis, 'abcdefghijklmnopqrstuvwxyz012345') - expect fetch_vm('testhost') - - delete "#{prefix}/vm/testhost", "", { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = true, http = 200) - - expect !fetch_vm('testhost') - end - end - end - end - - describe 'POST /vm/:hostname/snapshot' do - context '(auth not configured)' do - it 'creates a snapshot' do - create_vm('testhost', redis) - post "#{prefix}/vm/testhost/snapshot" - expect_json(ok = true, http = 202) - expect(JSON.parse(last_response.body)['testhost']['snapshot'].length).to be(32) - end - end - - context '(auth configured)' do - before(:each) do - app.settings.set :config, auth: true - end - - it 'returns a 401 if not authed' do - post "#{prefix}/vm/testhost/snapshot" - expect_json(ok = false, http = 401) - expect !has_vm_snapshot?('testhost', redis) - end - - it 'creates a snapshot if authed' do - create_vm('testhost', redis) - snapshot_vm('testhost', 'testsnapshot', redis) - - post "#{prefix}/vm/testhost/snapshot", "", { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = true, http = 202) - expect(JSON.parse(last_response.body)['testhost']['snapshot'].length).to be(32) - expect has_vm_snapshot?('testhost', redis) - end - end - end - - describe 'POST /vm/:hostname/snapshot/:snapshot' do - context '(auth not configured)' do - it 'reverts to a snapshot' do - create_vm('testhost', redis) - snapshot_vm('testhost', 'testsnapshot', redis) - - post "#{prefix}/vm/testhost/snapshot/testsnapshot" - expect_json(ok = true, http = 202) - expect vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot') - end - - it 'fails if the specified snapshot does not exist' do - create_vm('testhost', redis) - - post "#{prefix}/vm/testhost/snapshot/testsnapshot", "", { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = false, http = 404) - expect !vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot') - end - end - - context '(auth configured)' do - before(:each) do - app.settings.set :config, auth: true - end - - it 'returns a 401 if not authed' do - create_vm('testhost', redis) - snapshot_vm('testhost', 'testsnapshot', redis) - - post "#{prefix}/vm/testhost/snapshot/testsnapshot" - expect_json(ok = false, http = 401) - expect !vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot') - end - - it 'fails if authed and the specified snapshot does not exist' do - create_vm('testhost', redis) - - post "#{prefix}/vm/testhost/snapshot/testsnapshot", "", { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = false, http = 404) - expect !vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot') - end - - it 'reverts to a snapshot if authed' do - create_vm('testhost', redis) - snapshot_vm('testhost', 'testsnapshot', redis) - - post "#{prefix}/vm/testhost/snapshot/testsnapshot", "", { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = true, http = 202) - expect vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot') - end - end - end - end -end diff --git a/spec/integration/api/v1/vm_spec.rb b/spec/integration/api/v1/vm_spec.rb deleted file mode 100644 index 88d8b57..0000000 --- a/spec/integration/api/v1/vm_spec.rb +++ /dev/null @@ -1,435 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -describe Vmpooler::API::V1 do - include Rack::Test::Methods - - def app() - Vmpooler::API - end - - # Added to ensure no leakage in rack state from previous tests. - # Removes all routes, filters, middleware and extension hooks from the current class - # https://rubydoc.info/gems/sinatra/Sinatra/Base#reset!-class_method - before(:each) do - app.reset! - end - - describe '/vm' do - let(:prefix) { '/api/v1' } - let(:metrics) { Vmpooler::Metrics::DummyStatsd.new } - let(:config) { - { - config: { - 'site_name' => 'test pooler', - 'vm_lifetime_auth' => 2 - }, - pools: [ - {'name' => 'pool1', 'size' => 5}, - {'name' => 'pool2', 'size' => 10}, - {'name' => 'pool3', 'size' => 10} - ], - statsd: { 'prefix' => 'stats_prefix'}, - alias: { 'poolone' => ['pool1'] }, - pool_names: [ 'pool1', 'pool2', 'pool3', 'poolone', 'genericpool' ] - } - } - let(:current_time) { Time.now } - let(:vmname) { 'abcdefghijkl' } - let(:checkoutlock) { Mutex.new } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, metrics, nil) - app.settings.set :config, auth: false - app.settings.set :checkoutlock, checkoutlock - create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time) - end - - describe 'GET /vm/:hostname' do - it 'returns correct information on a running vm' do - create_running_vm 'pool1', vmname, redis - expect(TCPSocket).to receive(:gethostbyname).and_raise(RuntimeError) - get "#{prefix}/vm/#{vmname}" - expect_json(ok = true, http = 200) - response_body = (JSON.parse(last_response.body)[vmname]) - - expect(response_body["template"]).to eq("pool1") - expect(response_body["lifetime"]).to eq(0) - expect(response_body["running"]).to be >= 0 - expect(response_body["remaining"]).to be <= 0 - expect(response_body["start_time"]).to eq(current_time.to_datetime.rfc3339) - expect(response_body["end_time"]).to eq(current_time.to_datetime.rfc3339) - expect(response_body["state"]).to eq("running") - expect(response_body["ip"]).to eq("") - expect(response_body["host"]).to eq("host1") - end - end - - describe 'POST /vm' do - - let(:socket) { double('socket') } - it 'returns a single VM' do - create_ready_vm 'pool1', vmname, redis - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"1"}' - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: vmname - } - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'returns a single VM for an alias' do - create_ready_vm 'pool1', vmname, redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"poolone":"1"}' - expect_json(ok = true, http = 200) - - expected = { - ok: true, - poolone: { - hostname: vmname - } - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'fails on nonexistant pools' do - post "#{prefix}/vm", '{"poolpoolpool":"1"}' - expect_json(ok = false, http = 404) - end - - it 'returns 503 for empty pool when aliases are not defined' do - app.settings.config.delete(:alias) - app.settings.config[:pool_names] = ['pool1', 'pool2'] - - create_ready_vm 'pool1', vmname, redis - post "#{prefix}/vm/pool1" - post "#{prefix}/vm/pool1" - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns 503 for empty pool referenced by alias' do - create_ready_vm 'pool2', vmname, redis - post "#{prefix}/vm/poolone" - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns multiple VMs' do - create_ready_vm 'pool1', vmname, redis - create_ready_vm 'pool2', 'qrstuvwxyz012345', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}' - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: vmname - }, - pool2: { - hostname: 'qrstuvwxyz012345' - } - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'returns multiple VMs even when multiple instances from the same pool are requested' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - create_ready_vm 'pool1', '2abcdefghijklmnop', redis - create_ready_vm 'pool2', 'qrstuvwxyz012345', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"2","pool2":"1"}' - - expected = { - ok: true, - pool1: { - hostname: [ '1abcdefghijklmnop', '2abcdefghijklmnop' ] - }, - pool2: { - hostname: 'qrstuvwxyz012345' - } - } - - result = JSON.parse(last_response.body) - expect(result['ok']).to eq(true) - expect(result['pool1']['hostname']).to include('1abcdefghijklmnop', '2abcdefghijklmnop') - expect(result['pool2']['hostname']).to eq('qrstuvwxyz012345') - - expect_json(ok = true, http = 200) - end - - it 'returns multiple VMs even when multiple instances from multiple pools are requested' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - create_ready_vm 'pool1', '2abcdefghijklmnop', redis - create_ready_vm 'pool2', '1qrstuvwxyz012345', redis - create_ready_vm 'pool2', '2qrstuvwxyz012345', redis - create_ready_vm 'pool2', '3qrstuvwxyz012345', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"2","pool2":"3"}' - - expected = { - ok: true, - pool1: { - hostname: [ '1abcdefghijklmnop', '2abcdefghijklmnop' ] - }, - pool2: { - hostname: [ '1qrstuvwxyz012345', '2qrstuvwxyz012345', '3qrstuvwxyz012345' ] - } - } - - result = JSON.parse(last_response.body) - expect(result['ok']).to eq(true) - expect(result['pool1']['hostname']).to include('1abcdefghijklmnop', '2abcdefghijklmnop') - expect(result['pool2']['hostname']).to include('1qrstuvwxyz012345', '2qrstuvwxyz012345', '3qrstuvwxyz012345') - - expect_json(ok = true, http = 200) - end - - it 'returns VMs from multiple backend pools requested by an alias' do - Vmpooler::API.settings.config[:alias]['genericpool'] = ['pool1', 'pool2', 'pool3'] - - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - create_ready_vm 'pool2', '2abcdefghijklmnop', redis - create_ready_vm 'pool3', '1qrstuvwxyz012345', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"genericpool":"3"}' - - expected = { - ok: true, - genericpool: { - hostname: [ '1abcdefghijklmnop', '2abcdefghijklmnop', '1qrstuvwxyz012345' ] - } - } - - result = JSON.parse(last_response.body) - expect(result['ok']).to eq(true) - expect(result['genericpool']['hostname']).to include('1abcdefghijklmnop', '2abcdefghijklmnop', '1qrstuvwxyz012345') - - expect_json(ok = true, http = 200) - end - - it 'returns the first VM that was moved to the ready state when checking out a VM' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - create_ready_vm 'pool1', '2abcdefghijklmnop', redis - create_ready_vm 'pool1', '3abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"1"}' - - expected = { - ok: true, - "pool1": { - "hostname": "1abcdefghijklmnop" - } - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = true, http = 200) - end - - it 'fails when not all requested vms can be allocated' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - - post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns any checked out vms to their pools when not all requested vms can be allocated' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - - expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop', redis)).to eq(true) - end - - it 'fails when not all requested vms can be allocated, when requesting multiple instances from a pool' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - - post "#{prefix}/vm", '{"pool1":"2","pool2":"1"}' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns any checked out vms to their pools when not all requested vms can be allocated, when requesting multiple instances from a pool' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"2","pool2":"1"}' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - - expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop', redis)).to eq(true) - end - - it 'fails when not all requested vms can be allocated, when requesting multiple instances from multiple pools' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - - post "#{prefix}/vm", '{"pool1":"2","pool2":"3"}' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns any checked out vms to their pools when not all requested vms can be allocated, when requesting multiple instances from multiple pools' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - create_ready_vm 'pool1', '2abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"2","pool2":"3"}' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - - expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop', redis)).to eq(true) - expect(pool_has_ready_vm?('pool1', '2abcdefghijklmnop', redis)).to eq(true) - end - - it 'returns the second VM when the first fails to respond' do - create_ready_vm 'pool1', vmname, redis - create_ready_vm 'pool1', "2#{vmname}", redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).with(vmname, nil).and_raise('mockerror') - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).with("2#{vmname}", nil).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"1"}' - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: "2#{vmname}" - } - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - - expect(pool_has_ready_vm?('pool1', vmname, redis)).to be false - end - - context '(auth not configured)' do - it 'does not extend VM lifetime if auth token is provided' do - app.settings.set :config, auth: false - - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"1"}', { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: 'abcdefghijklmnop' - } - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - - vm = fetch_vm('abcdefghijklmnop') - expect(vm['lifetime']).to be_nil - end - end - - context '(auth configured)' do - it 'extends VM lifetime if auth token is provided' do - app.settings.set :config, auth: true - - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"1"}', { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: 'abcdefghijklmnop' - } - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - - vm = fetch_vm('abcdefghijklmnop') - expect(vm['lifetime'].to_i).to eq(2) - end - - it 'does not extend VM lifetime if auth token is not provided' do - app.settings.set :config, auth: true - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm", '{"pool1":"1"}' - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: 'abcdefghijklmnop' - } - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - - vm = fetch_vm('abcdefghijklmnop') - expect(vm['lifetime']).to be_nil - end - end - end - end -end diff --git a/spec/integration/api/v1/vm_template_spec.rb b/spec/integration/api/v1/vm_template_spec.rb deleted file mode 100644 index 83097e8..0000000 --- a/spec/integration/api/v1/vm_template_spec.rb +++ /dev/null @@ -1,327 +0,0 @@ -require 'spec_helper' -require 'rack/test' - -describe Vmpooler::API::V1 do - include Rack::Test::Methods - - def app() - Vmpooler::API - end - - # Added to ensure no leakage in rack state from previous tests. - # Removes all routes, filters, middleware and extension hooks from the current class - # https://rubydoc.info/gems/sinatra/Sinatra/Base#reset!-class_method - before(:each) do - app.reset! - end - - describe '/vm/:template' do - let(:prefix) { '/api/v1' } - let(:metrics) { Vmpooler::Metrics::DummyStatsd.new } - let(:config) { - { - config: { - 'site_name' => 'test pooler', - 'vm_lifetime_auth' => 2, - }, - providers: { vsphere: {} }, - pools: [ - {'name' => 'pool1', 'size' => 5}, - {'name' => 'pool2', 'size' => 10}, - {'name' => 'poolone', 'size' => 0} - ], - statsd: { 'prefix' => 'stats_prefix'}, - alias: { 'poolone' => 'pool1' }, - pool_names: [ 'pool1', 'pool2', 'poolone' ] - } - } - - let(:current_time) { Time.now } - let(:socket) { double('socket') } - let(:checkoutlock) { Mutex.new } - - before(:each) do - expect(app).to receive(:run!).once - app.execute([:api], config, redis, metrics, nil) - app.settings.set :config, auth: false - app.settings.set :checkoutlock, checkoutlock - create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time) - end - - describe 'POST /vm/:template' do - it 'returns a single VM' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1", '' - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: 'abcdefghijklmnop' - } - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'returns a single VM for an alias' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/poolone", '' - - expected = { - ok: true, - poolone: { - hostname: 'abcdefghijklmnop' - } - } - expect_json(ok = true, http = 200) - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'fails on nonexistant pools' do - post "#{prefix}/vm/poolpoolpool", '' - expect_json(ok = false, http = 404) - end - - it 'returns 503 for empty pool when aliases are not defined' do - app.settings.config.delete(:alias) - app.settings.config[:pool_names] = ['pool1', 'pool2'] - - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - post "#{prefix}/vm/pool1" - post "#{prefix}/vm/pool1" - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns 503 for empty pool referenced by alias' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - post "#{prefix}/vm/poolone" - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns multiple VMs' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - create_ready_vm 'pool2', 'qrstuvwxyz012345', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1+pool2", '' - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: 'abcdefghijklmnop' - }, - pool2: { - hostname: 'qrstuvwxyz012345' - } - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - end - - it 'returns multiple VMs even when multiple instances from multiple pools are requested' do - create_ready_vm 'pool1', '1abcdefghijklmnop', redis - create_ready_vm 'pool1', '2abcdefghijklmnop', redis - - create_ready_vm 'pool2', '1qrstuvwxyz012345', redis - create_ready_vm 'pool2', '2qrstuvwxyz012345', redis - create_ready_vm 'pool2', '3qrstuvwxyz012345', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1+pool1+pool2+pool2+pool2", '' - - expected = { - ok: true, - pool1: { - hostname: [ '1abcdefghijklmnop', '2abcdefghijklmnop' ] - }, - pool2: { - hostname: [ '1qrstuvwxyz012345', '2qrstuvwxyz012345', '3qrstuvwxyz012345' ] - } - } - - result = JSON.parse(last_response.body) - expect(result['ok']).to eq(true) - expect(result['pool1']['hostname']).to include('1abcdefghijklmnop', '2abcdefghijklmnop') - expect(result['pool2']['hostname']).to include('1qrstuvwxyz012345', '2qrstuvwxyz012345', '3qrstuvwxyz012345') - expect_json(ok = true, http = 200) - end - - it 'fails when not all requested vms can be allocated' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - post "#{prefix}/vm/pool1+pool2", '' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns any checked out vms to their pools when not all requested vms can be allocated' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1+pool2", '' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - - expect(pool_has_ready_vm?('pool1', 'abcdefghijklmnop', redis)).to eq(true) - end - - it 'fails when not all requested vms can be allocated, when requesting multiple instances from a pool' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - create_ready_vm 'pool1', '0123456789012345', redis - - post "#{prefix}/vm/pool1+pool1+pool2", '' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns any checked out vms to their pools when not all requested vms can be allocated, when requesting multiple instances from a pool' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - create_ready_vm 'pool1', '0123456789012345', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1+pool1+pool2", '' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - - expect(pool_has_ready_vm?('pool1', 'abcdefghijklmnop', redis)).to eq(true) - expect(pool_has_ready_vm?('pool1', '0123456789012345', redis)).to eq(true) - end - - it 'fails when not all requested vms can be allocated, when requesting multiple instances from multiple pools' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - create_ready_vm 'pool2', '0123456789012345', redis - - post "#{prefix}/vm/pool1+pool1+pool2+pool2+pool2", '' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - end - - it 'returns any checked out vms to their pools when not all requested vms can be allocated, when requesting multiple instances from multiple pools' do - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - create_ready_vm 'pool2', '0123456789012345', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1+pool1+pool2+pool2+pool2", '' - - expected = { ok: false } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - expect_json(ok = false, http = 503) - - expect(pool_has_ready_vm?('pool1', 'abcdefghijklmnop', redis)).to eq(true) - expect(pool_has_ready_vm?('pool2', '0123456789012345', redis)).to eq(true) - end - - context '(auth not configured)' do - it 'does not extend VM lifetime if auth token is provided' do - app.settings.set :config, auth: false - - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1", '', { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: 'abcdefghijklmnop' - } - } - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - vm = fetch_vm('abcdefghijklmnop') - expect(vm['lifetime']).to be_nil - end - end - - context '(auth configured)' do - it 'extends VM lifetime if auth token is provided' do - app.settings.set :config, auth: true - - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1", '', { - 'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345' - } - expect_json(ok = true, http = 200) - - expected = { - ok: true, - pool1: { - hostname: 'abcdefghijklmnop' - } - } - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - - vm = fetch_vm('abcdefghijklmnop') - expect(vm['lifetime'].to_i).to eq(2) - end - - it 'does not extend VM lifetime if auth token is not provided' do - app.settings.set :config, auth: true - create_ready_vm 'pool1', 'abcdefghijklmnop', redis - - allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket) - - post "#{prefix}/vm/pool1", '' - - expected = { - ok: true, - pool1: { - hostname: 'abcdefghijklmnop' - } - } - expect_json(ok = true, http = 200) - - expect(last_response.body).to eq(JSON.pretty_generate(expected)) - - vm = fetch_vm('abcdefghijklmnop') - expect(vm['lifetime']).to be_nil - end - end - end - end -end diff --git a/spec/integration/api/v2/vm_spec.rb b/spec/integration/api/v2/vm_spec.rb index b971ba8..411cbb9 100644 --- a/spec/integration/api/v2/vm_spec.rb +++ b/spec/integration/api/v2/vm_spec.rb @@ -341,6 +341,28 @@ describe Vmpooler::API::V2 do expect(pool_has_ready_vm?('pool1', '2abcdefghijklmnop', redis)).to eq(true) end + it 'returns the second VM when the first fails to respond' do + create_ready_vm 'pool1', vmname, redis + create_ready_vm 'pool1', "2#{vmname}", redis + + allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).with(vmname, nil).and_raise('mockerror') + allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).with("2#{vmname}", nil).and_return(socket) + + post "#{prefix}/vm", '{"pool1":"1"}' + expect_json(ok = true, http = 200) + + expected = { + ok: true, + pool1: { + hostname: "2#{vmname}" + } + } + + expect(last_response.body).to eq(JSON.pretty_generate(expected)) + + expect(pool_has_ready_vm?('pool1', vmname, redis)).to be false + end + context '(auth not configured)' do it 'does not extend VM lifetime if auth token is provided' do app.settings.set :config, auth: false