diff --git a/Gemfile b/Gemfile
index d301aba..e97d7cb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -13,6 +13,7 @@ gem 'statsd-ruby', '~> 1.4.0', :require => 'statsd'
gem 'connection_pool', '~> 2.2'
gem 'nokogiri', '~> 1.10'
gem 'spicy-proton', '~> 2.1'
+gem 'concurrent-ruby', '~> 1.1'
group :development do
gem 'pry'
diff --git a/README.md b/README.md
index 1a77553..d322131 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,7 @@ vmpooler provides configurable 'pools' of instantly-available (running) virtual
## Usage
-At [Puppet, Inc.](http://puppet.com) we run acceptance tests on thousands of disposable VMs every day. Dynamic cloning of VM templates initially worked fine for this, but added several seconds to each test run and was unable to account for failed clone tasks. By pushing these operations to a backend service, we were able to both speed up tests and eliminate test failures due to underlying infrastructure failures.
-
+At [Puppet, Inc.](http://puppet.com) we run acceptance tests on thousands of disposable VMs every day. Vmpooler manages the lifecycle of these VMs from request through deletion, with options available to pool ready instances, and provision on demand.
## Installation
@@ -85,7 +84,7 @@ docker run -it vmpooler manager
### docker-compose
-A docker-compose file is provided to support running vmpooler easily via docker-compose.
+A docker-compose file is provided to support running vmpooler easily via docker-compose. This is useful for development because your local code is used to build the gem used in the docker-compose environment.
```
docker-compose -f docker/docker-compose.yml up
@@ -113,7 +112,6 @@ A dashboard is provided to offer real-time statistics and historical graphs. It
## Command-line Utility
-- The [vmpooler_client.py](https://github.com/puppetlabs/vmpooler-client) CLI utility provides easy access to the vmpooler service. The tool is cross-platform and written in Python.
- [vmfloaty](https://github.com/briancain/vmfloaty) is a ruby based CLI tool and scripting library written in ruby.
## Vagrant plugin
diff --git a/bin/vmpooler b/bin/vmpooler
index 2f0f98f..3730c1f 100755
--- a/bin/vmpooler
+++ b/bin/vmpooler
@@ -7,6 +7,8 @@ config = Vmpooler.config
redis_host = config[:redis]['server']
redis_port = config[:redis]['port']
redis_password = config[:redis]['password']
+redis_connection_pool_size = config[:redis]['connection_pool_size']
+redis_connection_pool_timeout = config[:redis]['connection_pool_timeout']
logger_file = config[:config]['logfile']
metrics = Vmpooler.new_metrics(config)
@@ -36,7 +38,7 @@ if torun.include? 'manager'
Vmpooler::PoolManager.new(
config,
Vmpooler.new_logger(logger_file),
- Vmpooler.new_redis(redis_host, redis_port, redis_password),
+ Vmpooler.redis_connection_pool(redis_host, redis_port, redis_password, redis_connection_pool_size, redis_connection_pool_timeout, metrics),
metrics
).execute!
end
diff --git a/docker/Dockerfile b/docker/Dockerfile
index eb2ae6b..e879f1f 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -8,9 +8,9 @@
# RUN:
# docker run -e VMPOOLER_CONFIG -p 80:4567 -it vmpooler
-FROM jruby:9.2.9-jdk
+FROM jruby:9.2-jdk
-ARG vmpooler_version=0.5.0
+ARG vmpooler_version=0.11.3
COPY docker/docker-entrypoint.sh /usr/local/bin/
diff --git a/docker/Dockerfile_local b/docker/Dockerfile_local
index 0f6ed55..7a47516 100644
--- a/docker/Dockerfile_local
+++ b/docker/Dockerfile_local
@@ -8,7 +8,7 @@
# RUN:
# docker run -e VMPOOLER_CONFIG -p 80:4567 -it vmpooler
-FROM jruby:9.2.9-jdk
+FROM jruby:9.2-jdk
COPY docker/docker-entrypoint.sh /usr/local/bin/
COPY ./ ./
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 4ef776c..dd00a99 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -17,7 +17,7 @@ services:
- VMPOOLER_DEBUG=true # for use of dummy auth
- VMPOOLER_CONFIG_FILE=/etc/vmpooler/vmpooler.yaml
- REDIS_SERVER=redislocal
- - LOGFILE=/dev/stdout
+ - LOGFILE=/dev/null
image: vmpooler-local
depends_on:
- redislocal
diff --git a/docs/API.md b/docs/API.md
index d30b329..b4675ed 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -6,6 +6,7 @@
5. [VM snapshots](#vmsnapshots)
6. [Status and metrics](#statusmetrics)
7. [Pool configuration](#poolconfig)
+8. [Ondemand VM provisioning](#ondemandvm)
### API
@@ -799,3 +800,91 @@ $ curl -X POST -H "Content-Type: application/json" -d '{"debian-7-i386":"1"}' --
"ok": true
}
```
+
+#### Ondemand VM operations
+
+Ondemand VM operations offer a user an option to directly request instances to be allocated for use. This can be very useful when supporting a wide range of images because idle instances can be eliminated.
+
+##### POST /ondemandvm
+
+All instance types requested must match a pool name or alias in the running application configuration, or a 404 code will be returned
+
+When a provisioning request is accepted the API will return an indication that the request is successful. You may then poll /ondemandvm to monitor request status.
+
+An authentication token is required in order to request instances on demand when authentication is configured.
+
+Responses:
+* 201 - Provisioning request accepted
+* 400 - Payload contains invalid JSON and cannot be parsed
+* 403 - Request exceeds the configured per pool maximum
+* 404 - A pool was requested, which is not available in the running configuration, or an unknown error occurred.
+* 409 - A request of the matching ID has already been created
+```
+$ curl -X POST -H "Content-Type: application/json" -d '{"debian-7-i386":"4"}' --url https://vmpooler.example.com/api/v1/ondemandvm
+```
+```json
+{
+ "ok": true,
+ "request_id": "e3ff6271-d201-4f31-a315-d17f4e15863a"
+}
+```
+
+##### GET /ondemandvm
+
+Get the status of an ondemandvm request that has already been posted.
+
+When the request is ready the ready status will change to 'true'.
+
+The number of instances pending vs ready will be reflected in the API response.
+
+Responses:
+* 200 - The API request was successful and the status is ok
+* 202 - The request is not ready yet
+* 404 - The request can not be found, or an unknown error occurred
+```
+$ curl https://vmpooler.example.com/api/v1/ondemandvm/e3ff6271-d201-4f31-a315-d17f4e15863a
+```
+```json
+{
+ "ok": true,
+ "request_id": "e3ff6271-d201-4f31-a315-d17f4e15863a",
+ "ready": false,
+ "debian-7-i386": {
+ "ready": "3",
+ "pending": "1"
+ }
+}
+```
+```json
+{
+ "ok": true,
+ "request_id": "e3ff6271-d201-4f31-a315-d17f4e15863a",
+ "ready": true,
+ "debian-7-i386": {
+ "hostname": [
+ "vm1",
+ "vm2",
+ "vm3",
+ "vm4"
+ ]
+ }
+}
+```
+
+##### DELETE /ondemandvm
+
+Delete a ondemand request
+
+Deleting a ondemand request will delete any instances created for the request and mark the backend data for expiration in two weeks. Any subsequent attempts to retrieve request data will indicate it has been deleted.
+
+Responses:
+* 200 - The API request was sucessful. A message will indicate if the request has already been deleted.
+* 404 - The request can not be found, or an unknown error occurred.
+```
+$ curl -X DELETE https://vmpooler.example.com/api/v1/ondemandvm/e3ff6271-d201-4f31-a315-d17f4e15863a
+```
+```json
+{
+ "ok": true
+}
+```
diff --git a/docs/configuration.md b/docs/configuration.md
index 26d485b..4846b94 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -74,6 +74,16 @@ The prefix to use while storing Graphite data.
The TCP port to communicate with the graphite server.
(optional; default: 2003)
+### MAX\_ONDEMAND\_INSTANCES\_PER\_REQUEST
+
+The maximum number of instances any individual ondemand request may contain per pool.
+(default: 10)
+
+### ONDEMAND\_REQUEST\_TTL
+
+The amount of time (in minutes) to give for a ondemand request to be fulfilled before considering it to have failed.
+(default: 5)
+
## Manager options
### TASK\_LIMIT
@@ -123,6 +133,11 @@ The target cluster VMs are cloned into (host with least VMs chosen)
How long (in minutes) before marking a clone as 'failed' and retrying.
(optional; default: 15)
+### READY\_TTL
+
+How long (in minutes) a ready VM should stay in the ready queue.
+(default: 60)
+
### MAX\_TRIES
Set the max number of times a connection should retry in VM providers. This optional setting allows a user to dial in retry limits to suit your environment.
@@ -130,7 +145,7 @@ Set the max number of times a connection should retry in VM providers. This opti
### RETRY\_FACTOR
-When retrying, each attempt sleeps for the try count * retry_factor.
+When retrying, each attempt sleeps for the try count * retry\_factor.
Increase this number to lengthen the delay between retry attempts.
This is particularly useful for instances with a large number of pools
to prevent a thundering herd when retrying connections.
@@ -183,6 +198,21 @@ The argument can accept a full path to a file, or multiple files comma separated
Expects a string value
(optional)
+### ONDEMAND\_CLONE\_LIMIT
+
+Maximum number of simultaneous clones to perform for ondemand provisioning requests.
+(default: 10)
+
+### REDIS\_CONNECTION\_POOL\_SIZE
+
+Maximum number of connections to utilize for the redis connection pool.
+(default: 10)
+
+### REDIS\_CONNECTION\_POOL\_TIMEOUT
+
+How long a task should wait (in seconds) for a redis connection when all connections are in use.
+(default: 5)
+
## API options
### AUTH\_PROVIDER
@@ -221,3 +251,8 @@ The name of your deployment.
Enable experimental API capabilities such as changing pool template and size without application restart
Expects a boolean value
(optional; default: false)
+
+### MAX\_LIFETIME\_UPPER\_LIMIT
+
+Specify a maximum lifetime that a VM may be extended to in hours.
+(optional)
diff --git a/docs/dev-setup.md b/docs/dev-setup.md
index 3a128b0..5999f09 100644
--- a/docs/dev-setup.md
+++ b/docs/dev-setup.md
@@ -1,13 +1,15 @@
# Setting up a vmpooler development environment
-## Requirements
+## Docker is the preferred development environment
+
+The docker compose file is the easiest way to get vmpooler running with any local code changes. The docker compose file expects to find a vmpooler.yaml configuration file in the root vmpooler directory. The file is mapped into the running container for the vmpooler application. This file primarily contains the pools configuration. Nearly all other configuration can be supplied with environment variables.
+
+## Requirements for local installation directly on your system (not recommended)
* Supported on OSX, Windows and Linux
* Ruby or JRuby
- Note - Ruby 1.x support will be removed so it is best to use more modern ruby versions
-
Note - It is recommended to user Bundler instead of installing gems into the system repository
* A local Redis server
diff --git a/lib/vmpooler.rb b/lib/vmpooler.rb
index 5ae00f1..c1fe206 100644
--- a/lib/vmpooler.rb
+++ b/lib/vmpooler.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
module Vmpooler
+ require 'concurrent'
require 'date'
require 'json'
require 'net/ldap'
@@ -58,9 +59,14 @@ module Vmpooler
# Set some configuration defaults
parsed_config[:config]['task_limit'] = string_to_int(ENV['TASK_LIMIT']) || parsed_config[:config]['task_limit'] || 10
+ parsed_config[:config]['ondemand_clone_limit'] = string_to_int(ENV['ONDEMAND_CLONE_LIMIT']) || parsed_config[:config]['ondemand_clone_limit'] || 10
+ parsed_config[:config]['max_ondemand_instances_per_request'] = string_to_int(ENV['MAX_ONDEMAND_INSTANCES_PER_REQUEST']) || parsed_config[:config]['max_ondemand_instances_per_request'] || 10
parsed_config[:config]['migration_limit'] = string_to_int(ENV['MIGRATION_LIMIT']) if ENV['MIGRATION_LIMIT']
parsed_config[:config]['vm_checktime'] = string_to_int(ENV['VM_CHECKTIME']) || parsed_config[:config]['vm_checktime'] || 1
parsed_config[:config]['vm_lifetime'] = string_to_int(ENV['VM_LIFETIME']) || parsed_config[:config]['vm_lifetime'] || 24
+ parsed_config[:config]['max_lifetime_upper_limit'] = string_to_int(ENV['MAX_LIFETIME_UPPER_LIMIT']) || parsed_config[:config]['max_lifetime_upper_limit']
+ parsed_config[:config]['ready_ttl'] = string_to_int(ENV['READY_TTL']) || parsed_config[:config]['ready_ttl'] || 60
+ parsed_config[:config]['ondemand_request_ttl'] = string_to_int(ENV['ONDEMAND_REQUEST_TTL']) || parsed_config[:config]['ondemand_request_ttl'] || 5
parsed_config[:config]['prefix'] = ENV['PREFIX'] || parsed_config[:config]['prefix'] || ''
parsed_config[:config]['logfile'] = ENV['LOGFILE'] if ENV['LOGFILE']
@@ -84,6 +90,8 @@ module Vmpooler
parsed_config[:redis]['port'] = string_to_int(ENV['REDIS_PORT']) if ENV['REDIS_PORT']
parsed_config[:redis]['password'] = ENV['REDIS_PASSWORD'] if ENV['REDIS_PASSWORD']
parsed_config[:redis]['data_ttl'] = string_to_int(ENV['REDIS_DATA_TTL']) || parsed_config[:redis]['data_ttl'] || 168
+ parsed_config[:redis]['connection_pool_size'] = string_to_int(ENV['REDIS_CONNECTION_POOL_SIZE']) || parsed_config[:redis]['connection_pool_size'] || 10
+ parsed_config[:redis]['connection_pool_timeout'] = string_to_int(ENV['REDIS_CONNECTION_POOL_TIMEOUT']) || parsed_config[:redis]['connection_pool_timeout'] || 5
parsed_config[:statsd] = parsed_config[:statsd] || {} if ENV['STATSD_SERVER']
parsed_config[:statsd]['server'] = ENV['STATSD_SERVER'] if ENV['STATSD_SERVER']
@@ -117,6 +125,7 @@ module Vmpooler
parsed_config[:pools].each do |pool|
parsed_config[:pool_names] << pool['name']
+ pool['ready_ttl'] ||= parsed_config[:config]['ready_ttl']
if pool['alias']
if pool['alias'].is_a?(Array)
pool['alias'].each do |pool_alias|
@@ -154,6 +163,19 @@ module Vmpooler
pools
end
+ def self.redis_connection_pool(host, port, password, size, timeout, metrics)
+ Vmpooler::PoolManager::GenericConnectionPool.new(
+ metrics: metrics,
+ metric_prefix: 'redis_connection_pool',
+ size: size,
+ timeout: timeout
+ ) do
+ connection = Concurrent::Hash.new
+ redis = new_redis(host, port, password)
+ connection['connection'] = redis
+ end
+ end
+
def self.new_redis(host = 'localhost', port = nil, password = nil)
Redis.new(host: host, port: port, password: password)
end
diff --git a/lib/vmpooler/api/helpers.rb b/lib/vmpooler/api/helpers.rb
index 0b98143..f696e52 100644
--- a/lib/vmpooler/api/helpers.rb
+++ b/lib/vmpooler/api/helpers.rb
@@ -238,7 +238,7 @@ module Vmpooler
queue[:running] = get_total_across_pools_redis_scard(pools, 'vmpooler__running__', backend)
queue[:completed] = get_total_across_pools_redis_scard(pools, 'vmpooler__completed__', backend)
- queue[:cloning] = backend.get('vmpooler__tasks__clone').to_i
+ queue[:cloning] = backend.get('vmpooler__tasks__clone').to_i + backend.get('vmpooler__tasks__ondemandclone').to_i
queue[:booting] = queue[:pending].to_i - queue[:cloning].to_i
queue[:booting] = 0 if queue[:booting] < 0
queue[:total] = queue[:pending].to_i + queue[:ready].to_i + queue[:running].to_i + queue[:completed].to_i
diff --git a/lib/vmpooler/api/v1.rb b/lib/vmpooler/api/v1.rb
index c588a4a..966e6a7 100644
--- a/lib/vmpooler/api/v1.rb
+++ b/lib/vmpooler/api/v1.rb
@@ -42,6 +42,68 @@ module Vmpooler
Vmpooler::API.settings.checkoutlock
end
+ def get_template_aliases(template)
+ result = []
+ aliases = Vmpooler::API.settings.config[:alias]
+ if aliases
+ result += aliases[template] if aliases[template].is_a?(Array)
+ template_backends << aliases[template] if aliases[template].is_a?(String)
+ end
+ result
+ end
+
+ def get_pool_weights(template_backends)
+ pool_index = pool_index(pools)
+ weighted_pools = {}
+ template_backends.each do |t|
+ next unless pool_index.key? t
+
+ index = pool_index[t]
+ clone_target = pools[index]['clone_target'] || config['clone_target']
+ next unless config.key?('backend_weight')
+
+ weight = config['backend_weight'][clone_target]
+ if weight
+ weighted_pools[t] = weight
+ end
+ end
+ weighted_pools
+ end
+
+ def count_selection(selection)
+ result = {}
+ selection.uniq.each do |poolname|
+ result[poolname] = selection.count(poolname)
+ end
+ result
+ end
+
+ def evaluate_template_aliases(template, count)
+ template_backends = []
+ template_backends << template if backend.sismember('vmpooler__pools', template)
+ selection = []
+ aliases = get_template_aliases(template)
+ if aliases
+ template_backends += aliases
+ weighted_pools = get_pool_weights(template_backends)
+
+ pickup = Pickup.new(weighted_pools) if weighted_pools.count == template_backends.count
+ count.to_i.times do
+ if pickup
+ selection << pickup.pick
+ else
+ selection << template_backends.sample
+ end
+ end
+ else
+ count.to_i.times do
+ selection << template
+ end
+ end
+
+ count_selection(selection)
+ end
+
def fetch_single_vm(template)
template_backends = [template]
aliases = Vmpooler::API.settings.config[:alias]
@@ -245,11 +307,9 @@ module Vmpooler
pool_index = pool_index(pools)
template_configs = backend.hgetall('vmpooler__config__template')
template_configs&.each do |poolname, template|
- if pool_index.include? poolname
- unless pools[pool_index[poolname]]['template'] == template
- pools[pool_index[poolname]]['template'] = template
- end
- end
+ next unless pool_index.include? poolname
+
+ pools[pool_index[poolname]]['template'] = template
end
end
@@ -257,11 +317,9 @@ module Vmpooler
pool_index = pool_index(pools)
poolsize_configs = backend.hgetall('vmpooler__config__poolsize')
poolsize_configs&.each do |poolname, size|
- if pool_index.include? poolname
- unless pools[pool_index[poolname]]['size'] == size.to_i
- pools[pool_index[poolname]]['size'] == size.to_i
- end
- end
+ next unless pool_index.include? poolname
+
+ pools[pool_index[poolname]]['size'] = size.to_i
end
end
@@ -269,14 +327,69 @@ module Vmpooler
pool_index = pool_index(pools)
clone_target_configs = backend.hgetall('vmpooler__config__clone_target')
clone_target_configs&.each do |poolname, clone_target|
- if pool_index.include? poolname
- unless pools[pool_index[poolname]]['clone_target'] == clone_target
- pools[pool_index[poolname]]['clone_target'] == clone_target
- end
- end
+ next unless pool_index.include? poolname
+
+ pools[pool_index[poolname]]['clone_target'] = clone_target
end
end
+ def too_many_requested?(payload)
+ payload&.each do |_poolname, count|
+ next unless count.to_i > config['max_ondemand_instances_per_request']
+
+ return true
+ end
+ false
+ end
+
+ def generate_ondemand_request(payload)
+ result = { 'ok': false }
+
+ requested_instances = payload.reject { |k, _v| k == 'request_id' }
+ if too_many_requested?(requested_instances)
+ result['message'] = "requested amount of instances exceeds the maximum #{config['max_ondemand_instances_per_request']}"
+ status 403
+ return result
+ end
+
+ score = Time.now.to_i
+ request_id = payload['request_id']
+ request_id ||= generate_request_id
+ result['request_id'] = request_id
+
+ if backend.exists("vmpooler__odrequest__#{request_id}")
+ result['message'] = "request_id '#{request_id}' has already been created"
+ status 409
+ return result
+ end
+
+ status 201
+
+ platforms_with_aliases = []
+ requested_instances.each do |poolname, count|
+ selection = evaluate_template_aliases(poolname, count)
+ selection.map { |selected_pool, selected_pool_count| platforms_with_aliases << "#{poolname}:#{selected_pool}:#{selected_pool_count}" }
+ end
+ platforms_string = platforms_with_aliases.join(',')
+
+ return result unless backend.zadd('vmpooler__provisioning__request', score, request_id)
+
+ backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
+ if Vmpooler::API.settings.config[:auth] and has_token?
+ backend.hset("vmpooler__odrequest__#{request_id}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
+ backend.hset("vmpooler__odrequest__#{request_id}", 'token:user',
+ backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user'))
+ end
+
+ result['domain'] = config['domain'] if config['domain']
+ result[:ok] = true
+ result
+ end
+
+ def generate_request_id
+ SecureRandom.uuid
+ end
+
get '/' do
sync_pool_sizes
redirect to('/dashboard/')
@@ -395,7 +508,7 @@ module Vmpooler
end
# for backwards compatibility, include separate "empty" stats in "status" block
- if ready == 0
+ if ready == 0 && max != 0
result[:status][:empty] ||= []
result[:status][:empty].push(pool['name'])
@@ -689,6 +802,61 @@ module Vmpooler
JSON.pretty_generate(result)
end
+ post "#{api_prefix}/ondemandvm/?" do
+ content_type :json
+
+ need_token! if Vmpooler::API.settings.config[:auth]
+
+ result = { 'ok' => false }
+
+ begin
+ payload = JSON.parse(request.body.read)
+
+ if payload
+ invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
+ if invalid.empty?
+ result = generate_ondemand_request(payload)
+ else
+ result[:bad_templates] = invalid
+ invalid.each do |bad_template|
+ metrics.increment('ondemandrequest.invalid.' + bad_template)
+ end
+ status 404
+ end
+ else
+ metrics.increment('ondemandrequest.invalid.unknown')
+ status 404
+ end
+ rescue JSON::ParserError
+ status 400
+ result = {
+ 'ok' => false,
+ 'message' => 'JSON payload could not be parsed'
+ }
+ end
+
+ JSON.pretty_generate(result)
+ end
+
+ get "#{api_prefix}/ondemandvm/:requestid/?" do
+ content_type :json
+
+ status 404
+ result = check_ondemand_request(params[:requestid])
+
+ JSON.pretty_generate(result)
+ end
+
+ delete "#{api_prefix}/ondemandvm/:requestid/?" do
+ content_type :json
+ need_token! if Vmpooler::API.settings.config[:auth]
+
+ status 404
+ result = delete_ondemand_request(params[:requestid])
+
+ JSON.pretty_generate(result)
+ end
+
post "#{api_prefix}/vm/?" do
content_type :json
result = { 'ok' => false }
@@ -764,6 +932,78 @@ module Vmpooler
invalid
end
+ def check_ondemand_request(request_id)
+ result = { 'ok' => false }
+ request_hash = backend.hgetall("vmpooler__odrequest__#{request_id}")
+ if request_hash.empty?
+ result['message'] = "no request found for request_id '#{request_id}'"
+ return result
+ end
+
+ result['request_id'] = request_id
+ result['ready'] = false
+ result['ok'] = true
+ status 202
+
+ if request_hash['status'] == 'ready'
+ result['ready'] = true
+ platform_parts = request_hash['requested'].split(',')
+ platform_parts.each do |platform|
+ pool_alias, pool, _count = platform.split(':')
+ instances = backend.smembers("vmpooler__#{request_id}__#{pool_alias}__#{pool}")
+ result[pool_alias] = { 'hostname': instances }
+ end
+ result['domain'] = config['domain'] if config['domain']
+ status 200
+ elsif request_hash['status'] == 'failed'
+ result['message'] = "The request failed to provision instances within the configured ondemand_request_ttl '#{config['ondemand_request_ttl']}'"
+ status 200
+ elsif request_hash['status'] == 'deleted'
+ result['message'] = 'The request has been deleted'
+ status 200
+ else
+ platform_parts = request_hash['requested'].split(',')
+ platform_parts.each do |platform|
+ pool_alias, pool, count = platform.split(':')
+ instance_count = backend.scard("vmpooler__#{request_id}__#{pool_alias}__#{pool}")
+ result[pool_alias] = {
+ 'ready': instance_count.to_s,
+ 'pending': (count.to_i - instance_count.to_i).to_s
+ }
+ end
+ end
+
+ result
+ end
+
+ def delete_ondemand_request(request_id)
+ result = { 'ok' => false }
+
+ platforms = backend.hget("vmpooler__odrequest__#{request_id}", 'requested')
+ unless platforms
+ result['message'] = "no request found for request_id '#{request_id}'"
+ return result
+ end
+
+ if backend.hget("vmpooler__odrequest__#{request_id}", 'status') == 'deleted'
+ result['message'] = 'the request has already been deleted'
+ else
+ backend.hset("vmpooler__odrequest__#{request_id}", 'status', 'deleted')
+
+ platforms.split(',').each do |platform|
+ pool_alias, pool, _count = platform.split(':')
+ backend.smembers("vmpooler__#{request_id}__#{pool_alias}__#{pool}")&.each do |vm|
+ backend.smove("vmpooler__running__#{pool}", "vmpooler__completed__#{pool}", vm)
+ end
+ backend.del("vmpooler__#{request_id}__#{pool_alias}__#{pool}")
+ end
+ backend.expire("vmpooler__odrequest__#{request_id}", 129_600_0)
+ end
+ status 200
+ result['ok'] = true
+ result
+ end
+
post "#{api_prefix}/vm/:template/?" do
content_type :json
result = { 'ok' => false }
@@ -923,6 +1163,7 @@ module Vmpooler
unless arg.to_i > 0
failure.push("You provided a lifetime (#{arg}) but you must provide a positive number.")
end
+
when 'tags'
unless arg.is_a?(Hash)
failure.push("You provided tags (#{arg}) as something other than a hash.")
@@ -1047,7 +1288,7 @@ module Vmpooler
invalid.each do |bad_template|
metrics.increment("config.invalid.#{bad_template}")
end
- result[:bad_templates] = invalid
+ result[:not_configured] = invalid
status 400
end
else
diff --git a/lib/vmpooler/generic_connection_pool.rb b/lib/vmpooler/generic_connection_pool.rb
index 6ae916b..9e9fc0c 100644
--- a/lib/vmpooler/generic_connection_pool.rb
+++ b/lib/vmpooler/generic_connection_pool.rb
@@ -15,35 +15,17 @@ module Vmpooler
@metric_prefix = 'connectionpool' if @metric_prefix.nil? || @metric_prefix == ''
end
- if Thread.respond_to?(:handle_interrupt)
- # MRI
- def with_metrics(options = {})
- Thread.handle_interrupt(Exception => :never) do
- start = Time.now
- conn = checkout(options)
- timespan_ms = ((Time.now - start) * 1000).to_i
- @metrics&.gauge(@metric_prefix + '.available', @available.length)
- @metrics&.timing(@metric_prefix + '.waited', timespan_ms)
- begin
- Thread.handle_interrupt(Exception => :immediate) do
- yield conn
- end
- ensure
- checkin
- @metrics&.gauge(@metric_prefix + '.available', @available.length)
- end
- end
- end
- else
- # jruby 1.7.x
- def with_metrics(options = {})
+ def with_metrics(options = {})
+ Thread.handle_interrupt(Exception => :never) do
start = Time.now
conn = checkout(options)
timespan_ms = ((Time.now - start) * 1000).to_i
@metrics&.gauge(@metric_prefix + '.available', @available.length)
@metrics&.timing(@metric_prefix + '.waited', timespan_ms)
begin
- yield conn
+ Thread.handle_interrupt(Exception => :immediate) do
+ yield conn
+ end
ensure
checkin
@metrics&.gauge(@metric_prefix + '.available', @available.length)
diff --git a/lib/vmpooler/pool_manager.rb b/lib/vmpooler/pool_manager.rb
index 65847e8..f386b1f 100644
--- a/lib/vmpooler/pool_manager.rb
+++ b/lib/vmpooler/pool_manager.rb
@@ -9,7 +9,7 @@ module Vmpooler
CHECK_LOOP_DELAY_MAX_DEFAULT = 60
CHECK_LOOP_DELAY_DECAY_DEFAULT = 2.0
- def initialize(config, logger, redis, metrics)
+ def initialize(config, logger, redis_connection_pool, metrics)
$config = config
# Load logger library
@@ -18,19 +18,19 @@ module Vmpooler
# metrics logging handle
$metrics = metrics
- # Connect to Redis
- $redis = redis
+ # Redis connection pool
+ @redis = redis_connection_pool
# VM Provider objects
- $providers = {}
+ $providers = Concurrent::Hash.new
# Our thread-tracker object
- $threads = {}
+ $threads = Concurrent::Hash.new
# Pool mutex
- @reconfigure_pool = {}
+ @reconfigure_pool = Concurrent::Hash.new
- @vm_mutex = {}
+ @vm_mutex = Concurrent::Hash.new
# Name generator for generating host names
@name_generator = Spicy::Proton.new
@@ -45,24 +45,26 @@ module Vmpooler
# Place pool configuration in redis so an API instance can discover running pool configuration
def load_pools_to_redis
- previously_configured_pools = $redis.smembers('vmpooler__pools')
- currently_configured_pools = []
- config[:pools].each do |pool|
- currently_configured_pools << pool['name']
- $redis.sadd('vmpooler__pools', pool['name'])
- pool_keys = pool.keys
- pool_keys.delete('alias')
- to_set = {}
- pool_keys.each do |k|
- to_set[k] = pool[k]
+ @redis.with_metrics do |redis|
+ previously_configured_pools = redis.smembers('vmpooler__pools')
+ currently_configured_pools = []
+ config[:pools].each do |pool|
+ currently_configured_pools << pool['name']
+ redis.sadd('vmpooler__pools', pool['name'])
+ pool_keys = pool.keys
+ pool_keys.delete('alias')
+ to_set = {}
+ pool_keys.each do |k|
+ to_set[k] = pool[k]
+ end
+ to_set['alias'] = pool['alias'].join(',') if to_set.key?('alias')
+ redis.hmset("vmpooler__pool__#{pool['name']}", to_set.to_a.flatten) unless to_set.empty?
end
- to_set['alias'] = pool['alias'].join(',') if to_set.key?('alias')
- $redis.hmset("vmpooler__pool__#{pool['name']}", to_set.to_a.flatten) unless to_set.empty?
- end
- previously_configured_pools.each do |pool|
- unless currently_configured_pools.include? pool
- $redis.srem('vmpooler__pools', pool)
- $redis.del("vmpooler__pool__#{pool}")
+ previously_configured_pools.each do |pool|
+ unless currently_configured_pools.include? pool
+ redis.srem('vmpooler__pools', pool)
+ redis.del("vmpooler__pool__#{pool}")
+ end
end
end
nil
@@ -75,7 +77,9 @@ module Vmpooler
_check_pending_vm(vm, pool, timeout, provider)
rescue StandardError => e
$logger.log('s', "[!] [#{pool}] '#{vm}' #{timeout} #{provider} errored while checking a pending vm : #{e}")
- fail_pending_vm(vm, pool, timeout)
+ @redis.with_metrics do |redis|
+ fail_pending_vm(vm, pool, timeout, redis)
+ end
raise
end
end
@@ -86,31 +90,38 @@ module Vmpooler
return if mutex.locked?
mutex.synchronize do
- if provider.vm_ready?(pool, vm)
- move_pending_vm_to_ready(vm, pool)
- else
- fail_pending_vm(vm, pool, timeout)
+ @redis.with_metrics do |redis|
+ request_id = redis.hget("vmpooler__vm__#{vm}", 'request_id')
+ if provider.vm_ready?(pool, vm)
+ move_pending_vm_to_ready(vm, pool, redis, request_id)
+ else
+ fail_pending_vm(vm, pool, timeout, redis)
+ end
end
end
end
- def remove_nonexistent_vm(vm, pool)
- $redis.srem("vmpooler__pending__#{pool}", vm)
+ def remove_nonexistent_vm(vm, pool, redis)
+ redis.srem("vmpooler__pending__#{pool}", vm)
$logger.log('d', "[!] [#{pool}] '#{vm}' no longer exists. Removing from pending.")
end
- def fail_pending_vm(vm, pool, timeout, exists = true)
- clone_stamp = $redis.hget("vmpooler__vm__#{vm}", 'clone')
- return true unless clone_stamp
+ def fail_pending_vm(vm, pool, timeout, redis, exists = true)
+ clone_stamp = redis.hget("vmpooler__vm__#{vm}", 'clone')
time_since_clone = (Time.now - Time.parse(clone_stamp)) / 60
if time_since_clone > timeout
if exists
- $redis.smove('vmpooler__pending__' + pool, 'vmpooler__completed__' + pool, vm)
+ request_id = redis.hget("vmpooler__vm__#{vm}", 'request_id')
+ pool_alias = redis.hget("vmpooler__vm__#{vm}", 'pool_alias') if request_id
+ redis.multi
+ redis.smove('vmpooler__pending__' + pool, 'vmpooler__completed__' + pool, vm)
+ redis.zadd('vmpooler__odcreate__task', 1, "#{pool_alias}:#{pool}:1:#{request_id}") if request_id
+ redis.exec
$metrics.increment("errors.markedasfailed.#{pool}")
$logger.log('d', "[!] [#{pool}] '#{vm}' marked as 'failed' after #{timeout} minutes")
else
- remove_nonexistent_vm(vm, pool)
+ remove_nonexistent_vm(vm, pool, redis)
end
end
true
@@ -119,28 +130,53 @@ module Vmpooler
false
end
- def move_pending_vm_to_ready(vm, pool)
- clone_time = $redis.hget('vmpooler__vm__' + vm, 'clone')
- finish = format('%.2f', time: Time.now - Time.parse(clone_time)) if clone_time
+ def move_pending_vm_to_ready(vm, pool, redis, request_id = nil)
+ clone_time = redis.hget('vmpooler__vm__' + vm, 'clone')
+ finish = format('%.2f', time: Time.now - Time.parse(clone_time))
- $redis.smove('vmpooler__pending__' + pool, 'vmpooler__ready__' + pool, vm)
- $redis.hset('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm, finish) # maybe remove as this is never used by vmpooler itself?
- $redis.hset("vmpooler__vm__#{vm}", 'ready', Time.now)
+ if request_id
+ ondemandrequest_hash = redis.hgetall("vmpooler__odrequest__#{request_id}")
+ if ondemandrequest_hash['status'] == 'failed'
+ move_vm_queue(pool, vm, 'pending', 'completed', redis, "moved to completed queue. '#{request_id}' could not be filled in time")
+ return nil
+ elsif ondemandrequest_hash['status'] == 'deleted'
+ move_vm_queue(pool, vm, 'pending', 'completed', redis, "moved to completed queue. '#{request_id}' has been deleted")
+ return nil
+ end
+ pool_alias = redis.hget("vmpooler__vm__#{vm}", 'pool_alias')
- # last boot time is displayed in API, and used by alarming script
- $redis.hset('vmpooler__lastboot', pool, Time.now)
+ redis.pipelined do
+ redis.hset("vmpooler__active__#{pool}", vm, Time.now)
+ redis.hset("vmpooler__vm__#{vm}", 'checkout', Time.now)
+ redis.hset("vmpooler__vm__#{vm}", 'token:token', ondemandrequest_hash['token:token']) if ondemandrequest_hash['token:token']
+ redis.hset("vmpooler__vm__#{vm}", 'token:user', ondemandrequest_hash['token:user']) if ondemandrequest_hash['token:user']
+ redis.sadd("vmpooler__#{request_id}__#{pool_alias}__#{pool}", vm)
+ end
+ move_vm_queue(pool, vm, 'pending', 'running', redis)
+ else
+ redis.smove('vmpooler__pending__' + pool, 'vmpooler__ready__' + pool, vm)
+ end
+
+ redis.pipelined do
+ redis.hset('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm, finish) # maybe remove as this is never used by vmpooler itself?
+ redis.hset("vmpooler__vm__#{vm}", 'ready', Time.now)
+
+ # last boot time is displayed in API, and used by alarming script
+ redis.hset('vmpooler__lastboot', pool, Time.now)
+ end
$metrics.timing("time_to_ready_state.#{pool}", finish)
- $logger.log('s', "[>] [#{pool}] '#{vm}' moved from 'pending' to 'ready' queue")
+ $logger.log('s', "[>] [#{pool}] '#{vm}' moved from 'pending' to 'ready' queue") unless request_id
+ $logger.log('s', "[>] [#{pool}] '#{vm}' is 'ready' for request '#{request_id}'") if request_id
end
- def vm_still_ready?(pool_name, vm_name, provider)
+ def vm_still_ready?(pool_name, vm_name, provider, redis)
# Check if the VM is still ready/available
return true if provider.vm_ready?(pool_name, vm_name)
raise("VM #{vm_name} is not ready")
rescue StandardError
- move_vm_queue(pool_name, vm_name, 'ready', 'completed', "is unreachable, removed from 'ready' queue")
+ move_vm_queue(pool_name, vm_name, 'ready', 'completed', redis, "is unreachable, removed from 'ready' queue")
end
def check_ready_vm(vm, pool_name, ttl, provider)
@@ -160,34 +196,35 @@ module Vmpooler
return if mutex.locked?
mutex.synchronize do
- check_stamp = $redis.hget('vmpooler__vm__' + vm, 'check')
- return if check_stamp && (((Time.now - Time.parse(check_stamp)) / 60) <= $config[:config]['vm_checktime'])
+ @redis.with_metrics do |redis|
+ check_stamp = redis.hget('vmpooler__vm__' + vm, 'check')
+ last_checked_too_soon = ((Time.now - Time.parse(check_stamp)).to_i < $config[:config]['vm_checktime'] * 60) if check_stamp
+ break if check_stamp && last_checked_too_soon
- $redis.hset('vmpooler__vm__' + vm, 'check', Time.now)
- # Check if the hosts TTL has expired
- if ttl > 0
+ redis.hset('vmpooler__vm__' + vm, 'check', Time.now)
+ # Check if the hosts TTL has expired
# if 'boottime' is nil, set bootime to beginning of unix epoch, forces TTL to be assumed expired
- boottime = $redis.hget("vmpooler__vm__#{vm}", 'ready')
+ boottime = redis.hget("vmpooler__vm__#{vm}", 'ready')
if boottime
boottime = Time.parse(boottime)
else
boottime = Time.at(0)
end
- if ((Time.now - boottime) / 60).to_s[/^\d+\.\d{1}/].to_f > ttl
- $redis.smove('vmpooler__ready__' + pool_name, 'vmpooler__completed__' + pool_name, vm)
+ if (Time.now - boottime).to_i > ttl * 60
+ redis.smove('vmpooler__ready__' + pool_name, 'vmpooler__completed__' + pool_name, vm)
$logger.log('d', "[!] [#{pool_name}] '#{vm}' reached end of TTL after #{ttl} minutes, removed from 'ready' queue")
- return
+ return nil
end
+
+ break if mismatched_hostname?(vm, pool_name, provider, redis)
+
+ vm_still_ready?(pool_name, vm, provider, redis)
end
-
- return if mismatched_hostname?(vm, pool_name, provider)
-
- vm_still_ready?(pool_name, vm, provider)
end
end
- def mismatched_hostname?(vm, pool_name, provider)
+ def mismatched_hostname?(vm, pool_name, provider, redis)
pool_config = $config[:pools][$config[:pool_index][pool_name]]
check_hostname = pool_config['check_hostname_for_mismatch']
check_hostname = $config[:config]['check_ready_vm_hostname_for_mismatch'] if check_hostname.nil?
@@ -196,7 +233,7 @@ module Vmpooler
# Wait one minute before checking a VM for hostname mismatch
# When checking as soon as the VM passes the ready test the instance
# often doesn't report its hostname yet causing the VM to be removed immediately
- vm_ready_time = $redis.hget("vmpooler__vm__#{vm}", 'ready')
+ vm_ready_time = redis.hget("vmpooler__vm__#{vm}", 'ready')
if vm_ready_time
wait_before_checking = 60
time_since_ready = (Time.now - Time.parse(vm_ready_time)).to_i
@@ -213,7 +250,7 @@ module Vmpooler
return if hostname.empty?
return if hostname == vm
- $redis.smove('vmpooler__ready__' + pool_name, 'vmpooler__completed__' + pool_name, vm)
+ redis.smove('vmpooler__ready__' + pool_name, 'vmpooler__completed__' + pool_name, vm)
$logger.log('d', "[!] [#{pool_name}] '#{vm}' has mismatched hostname #{hostname}, removed from 'ready' queue")
true
end
@@ -234,49 +271,61 @@ module Vmpooler
return if mutex.locked?
mutex.synchronize do
- # Check that VM is within defined lifetime
- checkouttime = $redis.hget('vmpooler__active__' + pool, vm)
- if checkouttime
- running = (Time.now - Time.parse(checkouttime)) / 60 / 60
+ catch :stop_checking do
+ @redis.with_metrics do |redis|
+ # Check that VM is within defined lifetime
+ checkouttime = redis.hget('vmpooler__active__' + pool, vm)
+ if checkouttime
+ time_since_checkout = Time.now - Time.parse(checkouttime)
+ running = time_since_checkout / 60 / 60
- if (ttl.to_i > 0) && (running.to_i >= ttl.to_i)
- move_vm_queue(pool, vm, 'running', 'completed', "reached end of TTL after #{ttl} hours")
- return
- end
- end
+ if (ttl.to_i > 0) && (running.to_i >= ttl.to_i)
+ move_vm_queue(pool, vm, 'running', 'completed', redis, "reached end of TTL after #{ttl} hours")
+ throw :stop_checking
+ end
+ end
- if provider.vm_ready?(pool, vm)
- return
- else
- host = provider.get_vm(pool, vm)
+ if provider.vm_ready?(pool, vm)
+ throw :stop_checking
+ else
+ host = provider.get_vm(pool, vm)
- if host
- return
- else
- move_vm_queue(pool, vm, 'running', 'completed', 'is no longer in inventory, removing from running')
+ if host
+ throw :stop_checking
+ else
+ move_vm_queue(pool, vm, 'running', 'completed', redis, 'is no longer in inventory, removing from running')
+ end
+ end
end
end
end
end
- def move_vm_queue(pool, vm, queue_from, queue_to, msg = nil)
- $redis.smove("vmpooler__#{queue_from}__#{pool}", "vmpooler__#{queue_to}__#{pool}", vm)
+ def move_vm_queue(pool, vm, queue_from, queue_to, redis, msg = nil)
+ redis.smove("vmpooler__#{queue_from}__#{pool}", "vmpooler__#{queue_to}__#{pool}", vm)
$logger.log('d', "[!] [#{pool}] '#{vm}' #{msg}") if msg
end
# Clone a VM
- def clone_vm(pool_name, provider)
+ def clone_vm(pool_name, provider, request_id = nil, pool_alias = nil)
Thread.new do
begin
- _clone_vm(pool_name, provider)
+ _clone_vm(pool_name, provider, request_id, pool_alias)
rescue StandardError => e
- $logger.log('s', "[!] [#{pool_name}] failed while cloning VM with an error: #{e}")
+ if request_id
+ $logger.log('s', "[!] [#{pool_name}] failed while cloning VM for request #{request_id} with an error: #{e}")
+ @redis.with_metrics do |redis|
+ redis.zadd('vmpooler__odcreate__task', 1, "#{pool_alias}:#{pool_name}:1:#{request_id}")
+ end
+ else
+ $logger.log('s', "[!] [#{pool_name}] failed while cloning VM with an error: #{e}")
+ end
raise
end
end
end
- def generate_and_check_hostname(_pool_name)
+ def generate_and_check_hostname
# Generate a randomized hostname. The total name must no longer than 15
# character including the hyphen. The shortest adjective in the corpus is
# three characters long. Therefore, we can technically select a noun up to 11
@@ -285,20 +334,22 @@ module Vmpooler
# letter adjectives, we actually limit the noun to 10 letters to avoid
# inviting more conflicts. We favor selecting a longer noun rather than a
# longer adjective because longer adjectives tend to be less fun.
- noun = @name_generator.noun(max: 10)
- adjective = @name_generator.adjective(max: 14 - noun.length)
- random_name = [adjective, noun].join('-')
- hostname = $config[:config]['prefix'] + random_name
- available = $redis.hlen('vmpooler__vm__' + hostname) == 0
+ @redis.with do |redis|
+ noun = @name_generator.noun(max: 10)
+ adjective = @name_generator.adjective(max: 14 - noun.length)
+ random_name = [adjective, noun].join('-')
+ hostname = $config[:config]['prefix'] + random_name
+ available = redis.hlen('vmpooler__vm__' + hostname) == 0
- [hostname, available]
+ [hostname, available]
+ end
end
def find_unique_hostname(pool_name)
hostname_retries = 0
max_hostname_retries = 3
while hostname_retries < max_hostname_retries
- hostname, available = generate_and_check_hostname(pool_name)
+ hostname, available = generate_and_check_hostname
break if available
hostname_retries += 1
@@ -311,32 +362,52 @@ module Vmpooler
hostname
end
- def _clone_vm(pool_name, provider)
+ def _clone_vm(pool_name, provider, request_id = nil, pool_alias = nil)
new_vmname = find_unique_hostname(pool_name)
+ mutex = vm_mutex(new_vmname)
+ mutex.synchronize do
+ @redis.with_metrics do |redis|
+ # Add VM to Redis inventory ('pending' pool)
+ redis.multi
+ redis.sadd('vmpooler__pending__' + pool_name, new_vmname)
+ redis.hset('vmpooler__vm__' + new_vmname, 'clone', Time.now)
+ redis.hset('vmpooler__vm__' + new_vmname, 'template', pool_name) # This value is used to represent the pool.
+ redis.hset('vmpooler__vm__' + new_vmname, 'pool', pool_name)
+ redis.hset('vmpooler__vm__' + new_vmname, 'request_id', request_id) if request_id
+ redis.hset('vmpooler__vm__' + new_vmname, 'pool_alias', pool_alias) if pool_alias
+ redis.exec
+ end
- # Add VM to Redis inventory ('pending' pool)
- $redis.sadd('vmpooler__pending__' + pool_name, new_vmname)
- $redis.hset('vmpooler__vm__' + new_vmname, 'clone', Time.now)
- $redis.hset('vmpooler__vm__' + new_vmname, 'template', pool_name)
+ begin
+ $logger.log('d', "[ ] [#{pool_name}] Starting to clone '#{new_vmname}'")
+ start = Time.now
+ provider.create_vm(pool_name, new_vmname)
+ finish = format('%.2f', time: Time.now - start)
- begin
- $logger.log('d', "[ ] [#{pool_name}] Starting to clone '#{new_vmname}'")
- start = Time.now
- provider.create_vm(pool_name, new_vmname)
- finish = format('%.2f', time: Time.now - start)
+ @redis.with_metrics do |redis|
+ redis.pipelined do
+ redis.hset('vmpooler__clone__' + Date.today.to_s, pool_name + ':' + new_vmname, finish)
+ redis.hset('vmpooler__vm__' + new_vmname, 'clone_time', finish)
+ end
+ end
+ $logger.log('s', "[+] [#{pool_name}] '#{new_vmname}' cloned in #{finish} seconds")
- $redis.hset('vmpooler__clone__' + Date.today.to_s, pool_name + ':' + new_vmname, finish)
- $redis.hset('vmpooler__vm__' + new_vmname, 'clone_time', finish)
- $logger.log('s', "[+] [#{pool_name}] '#{new_vmname}' cloned in #{finish} seconds")
-
- $metrics.timing("clone.#{pool_name}", finish)
- rescue StandardError
- $redis.srem("vmpooler__pending__#{pool_name}", new_vmname)
- expiration_ttl = $config[:redis]['data_ttl'].to_i * 60 * 60
- $redis.expire("vmpooler__vm__#{new_vmname}", expiration_ttl)
- raise
- ensure
- $redis.decr('vmpooler__tasks__clone')
+ $metrics.timing("clone.#{pool_name}", finish)
+ rescue StandardError
+ @redis.with_metrics do |redis|
+ redis.pipelined do
+ redis.srem("vmpooler__pending__#{pool_name}", new_vmname)
+ expiration_ttl = $config[:redis]['data_ttl'].to_i * 60 * 60
+ redis.expire("vmpooler__vm__#{new_vmname}", expiration_ttl)
+ end
+ end
+ raise
+ ensure
+ @redis.with_metrics do |redis|
+ redis.decr('vmpooler__tasks__ondemandclone') if request_id
+ redis.decr('vmpooler__tasks__clone') unless request_id
+ end
+ end
end
end
@@ -357,36 +428,42 @@ module Vmpooler
return if mutex.locked?
mutex.synchronize do
- $redis.hdel('vmpooler__active__' + pool, vm)
- $redis.hset('vmpooler__vm__' + vm, 'destroy', Time.now)
+ @redis.with_metrics do |redis|
+ redis.pipelined do
+ redis.hdel('vmpooler__active__' + pool, vm)
+ redis.hset('vmpooler__vm__' + vm, 'destroy', Time.now)
- # Auto-expire metadata key
- $redis.expire('vmpooler__vm__' + vm, ($config[:redis]['data_ttl'].to_i * 60 * 60))
+ # Auto-expire metadata key
+ redis.expire('vmpooler__vm__' + vm, ($config[:redis]['data_ttl'].to_i * 60 * 60))
+ end
- start = Time.now
+ start = Time.now
- provider.destroy_vm(pool, vm)
+ provider.destroy_vm(pool, vm)
- $redis.srem('vmpooler__completed__' + pool, vm)
+ redis.srem('vmpooler__completed__' + pool, vm)
- finish = format('%.2f', time: Time.now - start)
- $logger.log('s', "[-] [#{pool}] '#{vm}' destroyed in #{finish} seconds")
- $metrics.timing("destroy.#{pool}", finish)
- get_vm_usage_labels(vm)
+ finish = format('%.2f', time: Time.now - start)
+ $logger.log('s', "[-] [#{pool}] '#{vm}' destroyed in #{finish} seconds")
+ $metrics.timing("destroy.#{pool}", finish)
+ get_vm_usage_labels(vm, redis)
+ end
end
dereference_mutex(vm)
end
- def get_vm_usage_labels(vm)
+ def get_vm_usage_labels(vm, redis)
return unless $config[:config]['usage_stats']
- checkout = $redis.hget("vmpooler__vm__#{vm}", 'checkout')
+ redis.multi
+ redis.hget("vmpooler__vm__#{vm}", 'checkout')
+ redis.hget("vmpooler__vm__#{vm}", 'tag:jenkins_build_url')
+ redis.hget("vmpooler__vm__#{vm}", 'token:user')
+ redis.hget("vmpooler__vm__#{vm}", 'template')
+ checkout, jenkins_build_url, user, poolname = redis.exec
return if checkout.nil?
- jenkins_build_url = $redis.hget("vmpooler__vm__#{vm}", 'tag:jenkins_build_url')
- user = $redis.hget("vmpooler__vm__#{vm}", 'token:user') || 'unauthenticated'
- poolname = $redis.hget("vmpooler__vm__#{vm}", 'template')
-
+ user ||= 'unauthenticated'
unless jenkins_build_url
user = user.gsub('.', '_')
$metrics.increment("usage.#{user}.#{poolname}")
@@ -420,7 +497,8 @@ module Vmpooler
$metrics.increment(metric_parts.join('.'))
rescue StandardError => e
- logger.log('d', "[!] [#{poolname}] failed while evaluating usage labels on '#{vm}' with an error: #{e}")
+ $logger.log('d', "[!] [#{poolname}] failed while evaluating usage labels on '#{vm}' with an error: #{e}")
+ raise
end
def component_to_test(match, labels_string)
@@ -444,7 +522,7 @@ module Vmpooler
if provider_purge
Thread.new do
begin
- purge_vms_and_folders(provider.to_s)
+ purge_vms_and_folders($providers[provider.to_s])
rescue StandardError => e
$logger.log('s', "[!] failed while purging provider #{provider} VMs and folders with an error: #{e}")
end
@@ -455,13 +533,14 @@ module Vmpooler
end
# Return a list of pool folders
- def pool_folders(provider_name)
+ def pool_folders(provider)
+ provider_name = provider.name
folders = {}
$config[:pools].each do |pool|
next unless pool['provider'] == provider_name
folder_parts = pool['folder'].split('/')
- datacenter = $providers[provider_name].get_target_datacenter_from_config(pool['name'])
+ datacenter = provider.get_target_datacenter_from_config(pool['name'])
folders[folder_parts.pop] = "#{datacenter}/vm/#{folder_parts.join('/')}"
end
folders
@@ -478,8 +557,8 @@ module Vmpooler
def purge_vms_and_folders(provider)
configured_folders = pool_folders(provider)
base_folders = get_base_folders(configured_folders)
- whitelist = $providers[provider].provider_config['folder_whitelist']
- $providers[provider].purge_unconfigured_folders(base_folders, configured_folders, whitelist)
+ whitelist = provider.provider_config['folder_whitelist']
+ provider.purge_unconfigured_folders(base_folders, configured_folders, whitelist)
end
def create_vm_disk(pool_name, vm, disk_size, provider)
@@ -505,10 +584,12 @@ module Vmpooler
finish = format('%.2f', time: Time.now - start)
if result
- rdisks = $redis.hget('vmpooler__vm__' + vm_name, 'disk')
- disks = rdisks ? rdisks.split(':') : []
- disks.push("+#{disk_size}gb")
- $redis.hset('vmpooler__vm__' + vm_name, 'disk', disks.join(':'))
+ @redis.with_metrics do |redis|
+ rdisks = redis.hget('vmpooler__vm__' + vm_name, 'disk')
+ disks = rdisks ? rdisks.split(':') : []
+ disks.push("+#{disk_size}gb")
+ redis.hset('vmpooler__vm__' + vm_name, 'disk', disks.join(':'))
+ end
$logger.log('s', "[+] [disk_manager] '#{vm_name}' attached #{disk_size}gb disk in #{finish} seconds")
else
@@ -538,7 +619,9 @@ module Vmpooler
finish = format('%.2f', time: Time.now - start)
if result
- $redis.hset('vmpooler__vm__' + vm_name, 'snapshot:' + snapshot_name, Time.now.to_s)
+ @redis.with_metrics do |redis|
+ redis.hset('vmpooler__vm__' + vm_name, 'snapshot:' + snapshot_name, Time.now.to_s)
+ end
$logger.log('s', "[+] [snapshot_manager] '#{vm_name}' snapshot created in #{finish} seconds")
else
$logger.log('s', "[+] [snapshot_manager] Failed to snapshot '#{vm_name}'")
@@ -594,9 +677,9 @@ module Vmpooler
@default_providers ||= %w[vsphere dummy]
end
- def get_pool_name_for_vm(vm_name)
+ def get_pool_name_for_vm(vm_name, redis)
# the 'template' is a bad name. Should really be 'poolname'
- $redis.hget('vmpooler__vm__' + vm_name, 'template')
+ redis.hget('vmpooler__vm__' + vm_name, 'template')
end
# @param pool_name [String] - the name of the pool
@@ -628,19 +711,21 @@ module Vmpooler
end
def _check_disk_queue
- task_detail = $redis.spop('vmpooler__tasks__disk')
- unless task_detail.nil?
- begin
- vm_name, disk_size = task_detail.split(':')
- pool_name = get_pool_name_for_vm(vm_name)
- raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
+ @redis.with_metrics do |redis|
+ task_detail = redis.spop('vmpooler__tasks__disk')
+ unless task_detail.nil?
+ begin
+ vm_name, disk_size = task_detail.split(':')
+ pool_name = get_pool_name_for_vm(vm_name, redis)
+ raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
- provider = get_provider_for_pool(pool_name)
- raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
+ provider = get_provider_for_pool(pool_name)
+ raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
- create_vm_disk(pool_name, vm_name, disk_size, provider)
- rescue StandardError => e
- $logger.log('s', "[!] [disk_manager] disk creation appears to have failed: #{e}")
+ create_vm_disk(pool_name, vm_name, disk_size, provider)
+ rescue StandardError => e
+ $logger.log('s', "[!] [disk_manager] disk creation appears to have failed: #{e}")
+ end
end
end
end
@@ -664,37 +749,39 @@ module Vmpooler
end
def _check_snapshot_queue
- task_detail = $redis.spop('vmpooler__tasks__snapshot')
+ @redis.with_metrics do |redis|
+ task_detail = redis.spop('vmpooler__tasks__snapshot')
- unless task_detail.nil?
- begin
- vm_name, snapshot_name = task_detail.split(':')
- pool_name = get_pool_name_for_vm(vm_name)
- raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
+ unless task_detail.nil?
+ begin
+ vm_name, snapshot_name = task_detail.split(':')
+ pool_name = get_pool_name_for_vm(vm_name, redis)
+ raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
- provider = get_provider_for_pool(pool_name)
- raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
+ provider = get_provider_for_pool(pool_name)
+ raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
- create_vm_snapshot(pool_name, vm_name, snapshot_name, provider)
- rescue StandardError => e
- $logger.log('s', "[!] [snapshot_manager] snapshot create appears to have failed: #{e}")
+ create_vm_snapshot(pool_name, vm_name, snapshot_name, provider)
+ rescue StandardError => e
+ $logger.log('s', "[!] [snapshot_manager] snapshot create appears to have failed: #{e}")
+ end
end
- end
- task_detail = $redis.spop('vmpooler__tasks__snapshot-revert')
+ task_detail = redis.spop('vmpooler__tasks__snapshot-revert')
- unless task_detail.nil?
- begin
- vm_name, snapshot_name = task_detail.split(':')
- pool_name = get_pool_name_for_vm(vm_name)
- raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
+ unless task_detail.nil?
+ begin
+ vm_name, snapshot_name = task_detail.split(':')
+ pool_name = get_pool_name_for_vm(vm_name, redis)
+ raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
- provider = get_provider_for_pool(pool_name)
- raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
+ provider = get_provider_for_pool(pool_name)
+ raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
- revert_vm_snapshot(pool_name, vm_name, snapshot_name, provider)
- rescue StandardError => e
- $logger.log('s', "[!] [snapshot_manager] snapshot revert appears to have failed: #{e}")
+ revert_vm_snapshot(pool_name, vm_name, snapshot_name, provider)
+ rescue StandardError => e
+ $logger.log('s', "[!] [snapshot_manager] snapshot revert appears to have failed: #{e}")
+ end
end
end
end
@@ -704,7 +791,9 @@ module Vmpooler
begin
mutex = vm_mutex(vm_name)
mutex.synchronize do
- $redis.srem("vmpooler__migrating__#{pool_name}", vm_name)
+ @redis.with_metrics do |redis|
+ redis.srem("vmpooler__migrating__#{pool_name}", vm_name)
+ end
provider.migrate_vm(pool_name, vm_name)
end
rescue StandardError => e
@@ -737,47 +826,65 @@ module Vmpooler
wakeup_by = Time.now + wakeup_period
return if time_passed?(:exit_by, exit_by)
- initial_ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}") if options[:pool_size_change]
+ @redis.with_metrics do |redis|
+ initial_ready_size = redis.scard("vmpooler__ready__#{options[:poolname]}") if options[:pool_size_change]
- initial_clone_target = $redis.hget("vmpooler__pool__#{options[:poolname]}", options[:clone_target]) if options[:clone_target_change]
+ initial_clone_target = redis.hget("vmpooler__pool__#{options[:poolname]}", options[:clone_target]) if options[:clone_target_change]
- initial_template = $redis.hget('vmpooler__template__prepared', options[:poolname]) if options[:pool_template_change]
+ initial_template = redis.hget('vmpooler__template__prepared', options[:poolname]) if options[:pool_template_change]
- loop do
- sleep(1)
- break if time_passed?(:exit_by, exit_by)
+ loop do
+ sleep(1)
+ break if time_passed?(:exit_by, exit_by)
- # Check for wakeup events
- if time_passed?(:wakeup_by, wakeup_by)
- wakeup_by = Time.now + wakeup_period
+ # Check for wakeup events
+ if time_passed?(:wakeup_by, wakeup_by)
+ wakeup_by = Time.now + wakeup_period
- # Wakeup if the number of ready VMs has changed
- if options[:pool_size_change]
- ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}")
- break unless ready_size == initial_ready_size
- end
+ # Wakeup if the number of ready VMs has changed
+ if options[:pool_size_change]
+ ready_size = redis.scard("vmpooler__ready__#{options[:poolname]}")
+ break unless ready_size == initial_ready_size
+ end
- if options[:clone_target_change]
- clone_target = $redis.hget('vmpooler__config__clone_target}', options[:poolname])
- if clone_target
- break unless clone_target == initial_clone_target
+ if options[:clone_target_change]
+ clone_target = redis.hget('vmpooler__config__clone_target}', options[:poolname])
+ if clone_target
+ break unless clone_target == initial_clone_target
+ end
+ end
+
+ if options[:pool_template_change]
+ configured_template = redis.hget('vmpooler__config__template', options[:poolname])
+ if configured_template
+ break unless initial_template == configured_template
+ end
+ end
+
+ if options[:pool_reset]
+ pending = redis.sismember('vmpooler__poolreset', options[:poolname])
+ break if pending
+ end
+
+ if options[:pending_vm]
+ pending_vm_count = redis.scard("vmpooler__pending__#{options[:poolname]}")
+ break unless pending_vm_count == 0
+ end
+
+ if options[:ondemand_request]
+ redis.multi
+ redis.zcard('vmpooler__provisioning__request')
+ redis.zcard('vmpooler__provisioning__processing')
+ redis.zcard('vmpooler__odcreate__task')
+ od_request, od_processing, od_createtask = redis.exec
+ break unless od_request == 0
+ break unless od_processing == 0
+ break unless od_createtask == 0
end
end
- if options[:pool_template_change]
- configured_template = $redis.hget('vmpooler__config__template', options[:poolname])
- if configured_template
- break unless initial_template == configured_template
- end
- end
-
- if options[:pool_reset]
- break if $redis.sismember('vmpooler__poolreset', options[:poolname])
- end
-
+ break if time_passed?(:exit_by, exit_by)
end
-
- break if time_passed?(:exit_by, exit_by)
end
end
@@ -813,7 +920,7 @@ module Vmpooler
loop_delay = (loop_delay * loop_delay_decay).to_i
loop_delay = loop_delay_max if loop_delay > loop_delay_max
end
- sleep_with_wakeup_events(loop_delay, loop_delay_min, pool_size_change: true, poolname: pool['name'], pool_template_change: true, clone_target_change: true, pool_reset: true)
+ sleep_with_wakeup_events(loop_delay, loop_delay_min, pool_size_change: true, poolname: pool['name'], pool_template_change: true, clone_target_change: true, pending_vm: true, pool_reset: true)
unless maxloop == 0
break if loop_count >= maxloop
@@ -843,77 +950,84 @@ module Vmpooler
end
def sync_pool_template(pool)
- pool_template = $redis.hget('vmpooler__config__template', pool['name'])
- if pool_template
- pool['template'] = pool_template unless pool['template'] == pool_template
+ @redis.with_metrics do |redis|
+ pool_template = redis.hget('vmpooler__config__template', pool['name'])
+ if pool_template
+ pool['template'] = pool_template unless pool['template'] == pool_template
+ end
end
end
- def prepare_template(pool, provider)
+ def prepare_template(pool, provider, redis)
if $config[:config]['create_template_delta_disks']
- unless $redis.sismember('vmpooler__template__deltas', pool['template'])
+ unless redis.sismember('vmpooler__template__deltas', pool['template'])
begin
provider.create_template_delta_disks(pool)
- $redis.sadd('vmpooler__template__deltas', pool['template'])
+ redis.sadd('vmpooler__template__deltas', pool['template'])
rescue StandardError => e
$logger.log('s', "[!] [#{pool['name']}] failed while preparing a template with an error. As a result vmpooler could not create the template delta disks. Either a template delta disk already exists, or the template delta disk creation failed. The error is: #{e}")
end
end
end
- $redis.hset('vmpooler__template__prepared', pool['name'], pool['template'])
+ redis.hset('vmpooler__template__prepared', pool['name'], pool['template'])
end
def evaluate_template(pool, provider)
mutex = pool_mutex(pool['name'])
- prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
- configured_template = $redis.hget('vmpooler__config__template', pool['name'])
return if mutex.locked?
- if prepared_template.nil?
- mutex.synchronize do
- prepare_template(pool, provider)
- prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
- end
- elsif prepared_template != pool['template']
- if configured_template.nil?
+ catch :update_not_needed do
+ @redis.with_metrics do |redis|
+ prepared_template = redis.hget('vmpooler__template__prepared', pool['name'])
+ configured_template = redis.hget('vmpooler__config__template', pool['name'])
+
+ if prepared_template.nil?
+ mutex.synchronize do
+ prepare_template(pool, provider, redis)
+ prepared_template = redis.hget('vmpooler__template__prepared', pool['name'])
+ end
+ elsif prepared_template != pool['template']
+ if configured_template.nil?
+ mutex.synchronize do
+ prepare_template(pool, provider, redis)
+ prepared_template = redis.hget('vmpooler__template__prepared', pool['name'])
+ end
+ end
+ end
+ throw :update_not_needed if configured_template.nil?
+ throw :update_not_needed if configured_template == prepared_template
+
mutex.synchronize do
- prepare_template(pool, provider)
- prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
+ update_pool_template(pool, provider, configured_template, prepared_template, redis)
end
end
end
- return if configured_template.nil?
- return if configured_template == prepared_template
-
- mutex.synchronize do
- update_pool_template(pool, provider, configured_template, prepared_template)
- end
end
- def drain_pool(poolname)
+ def drain_pool(poolname, redis)
# Clear a pool of ready and pending instances
- if $redis.scard("vmpooler__ready__#{poolname}") > 0
+ if redis.scard("vmpooler__ready__#{poolname}") > 0
$logger.log('s', "[*] [#{poolname}] removing ready instances")
- $redis.smembers("vmpooler__ready__#{poolname}").each do |vm|
- move_vm_queue(poolname, vm, 'ready', 'completed')
+ redis.smembers("vmpooler__ready__#{poolname}").each do |vm|
+ move_vm_queue(poolname, vm, 'ready', 'completed', redis)
end
end
- if $redis.scard("vmpooler__pending__#{poolname}") > 0
+ if redis.scard("vmpooler__pending__#{poolname}") > 0
$logger.log('s', "[*] [#{poolname}] removing pending instances")
- $redis.smembers("vmpooler__pending__#{poolname}").each do |vm|
- move_vm_queue(poolname, vm, 'pending', 'completed')
+ redis.smembers("vmpooler__pending__#{poolname}").each do |vm|
+ move_vm_queue(poolname, vm, 'pending', 'completed', redis)
end
end
end
- def update_pool_template(pool, provider, configured_template, prepared_template)
+ def update_pool_template(pool, provider, configured_template, prepared_template, redis)
pool['template'] = configured_template
$logger.log('s', "[*] [#{pool['name']}] template updated from #{prepared_template} to #{configured_template}")
# Remove all ready and pending VMs so new instances are created from the new template
- drain_pool(pool['name'])
+ drain_pool(pool['name'], redis)
# Prepare template for deployment
$logger.log('s', "[*] [#{pool['name']}] preparing pool template for deployment")
- prepare_template(pool, provider)
+ prepare_template(pool, provider, redis)
$logger.log('s', "[*] [#{pool['name']}] is ready for use")
end
@@ -921,38 +1035,45 @@ module Vmpooler
mutex = pool_mutex(pool['name'])
return if mutex.locked?
- clone_target = $redis.hget('vmpooler__config__clone_target', pool['name'])
- return if clone_target.nil?
- return if clone_target == pool['clone_target']
+ @redis.with_metrics do |redis|
+ clone_target = redis.hget('vmpooler__config__clone_target', pool['name'])
+ break if clone_target.nil?
+ break if clone_target == pool['clone_target']
- $logger.log('s', "[*] [#{pool['name']}] clone updated from #{pool['clone_target']} to #{clone_target}")
- mutex.synchronize do
- pool['clone_target'] = clone_target
- # Remove all ready and pending VMs so new instances are created for the new clone_target
- drain_pool(pool['name'])
+ $logger.log('s', "[*] [#{pool['name']}] clone updated from #{pool['clone_target']} to #{clone_target}")
+ mutex.synchronize do
+ pool['clone_target'] = clone_target
+ # Remove all ready and pending VMs so new instances are created for the new clone_target
+ drain_pool(pool['name'], redis)
+ end
+ $logger.log('s', "[*] [#{pool['name']}] is ready for use")
end
- $logger.log('s', "[*] [#{pool['name']}] is ready for use")
end
def remove_excess_vms(pool)
- ready = $redis.scard("vmpooler__ready__#{pool['name']}")
- total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
- return if total.nil?
- return if total == 0
+ @redis.with_metrics do |redis|
+ redis.multi
+ redis.scard("vmpooler__ready__#{pool['name']}")
+ redis.scard("vmpooler__pending__#{pool['name']}")
+ ready, pending = redis.exec
+ total = pending.to_i + ready.to_i
+ break if total.nil?
+ break if total == 0
- mutex = pool_mutex(pool['name'])
- return if mutex.locked?
- return unless ready > pool['size']
+ mutex = pool_mutex(pool['name'])
+ break if mutex.locked?
+ break unless ready.to_i > pool['size']
- mutex.synchronize do
- difference = ready - pool['size']
- difference.times do
- next_vm = $redis.spop("vmpooler__ready__#{pool['name']}")
- move_vm_queue(pool['name'], next_vm, 'ready', 'completed')
- end
- if total > ready
- $redis.smembers("vmpooler__pending__#{pool['name']}").each do |vm|
- move_vm_queue(pool['name'], vm, 'pending', 'completed')
+ mutex.synchronize do
+ difference = ready.to_i - pool['size']
+ difference.times do
+ next_vm = redis.spop("vmpooler__ready__#{pool['name']}")
+ move_vm_queue(pool['name'], next_vm, 'ready', 'completed', redis)
+ end
+ if total > ready
+ redis.smembers("vmpooler__pending__#{pool['name']}").each do |vm|
+ move_vm_queue(pool['name'], vm, 'pending', 'completed', redis)
+ end
end
end
end
@@ -962,26 +1083,30 @@ module Vmpooler
mutex = pool_mutex(pool['name'])
return if mutex.locked?
- poolsize = $redis.hget('vmpooler__config__poolsize', pool['name'])
- return if poolsize.nil?
+ @redis.with_metrics do |redis|
+ poolsize = redis.hget('vmpooler__config__poolsize', pool['name'])
+ break if poolsize.nil?
- poolsize = Integer(poolsize)
- return if poolsize == pool['size']
+ poolsize = Integer(poolsize)
+ break if poolsize == pool['size']
- mutex.synchronize do
- pool['size'] = poolsize
+ mutex.synchronize do
+ pool['size'] = poolsize
+ end
end
end
def reset_pool(pool)
poolname = pool['name']
- return unless $redis.sismember('vmpooler__poolreset', poolname)
+ @redis.with_metrics do |redis|
+ break unless redis.sismember('vmpooler__poolreset', poolname)
- $redis.srem('vmpooler__poolreset', poolname)
- mutex = pool_mutex(poolname)
- mutex.synchronize do
- drain_pool(poolname)
- $logger.log('s', "[*] [#{poolname}] reset has cleared ready and pending instances")
+ redis.srem('vmpooler__poolreset', poolname)
+ mutex = pool_mutex(poolname)
+ mutex.synchronize do
+ drain_pool(poolname, redis)
+ $logger.log('s', "[*] [#{poolname}] reset has cleared ready and pending instances")
+ end
end
end
@@ -990,21 +1115,23 @@ module Vmpooler
begin
mutex = pool_mutex(pool['name'])
mutex.synchronize do
- provider.vms_in_pool(pool['name']).each do |vm|
- if !$redis.sismember('vmpooler__running__' + pool['name'], vm['name']) &&
- !$redis.sismember('vmpooler__ready__' + pool['name'], vm['name']) &&
- !$redis.sismember('vmpooler__pending__' + pool['name'], vm['name']) &&
- !$redis.sismember('vmpooler__completed__' + pool['name'], vm['name']) &&
- !$redis.sismember('vmpooler__discovered__' + pool['name'], vm['name']) &&
- !$redis.sismember('vmpooler__migrating__' + pool['name'], vm['name'])
+ @redis.with_metrics do |redis|
+ provider.vms_in_pool(pool['name']).each do |vm|
+ if !redis.sismember('vmpooler__running__' + pool['name'], vm['name']) &&
+ !redis.sismember('vmpooler__ready__' + pool['name'], vm['name']) &&
+ !redis.sismember('vmpooler__pending__' + pool['name'], vm['name']) &&
+ !redis.sismember('vmpooler__completed__' + pool['name'], vm['name']) &&
+ !redis.sismember('vmpooler__discovered__' + pool['name'], vm['name']) &&
+ !redis.sismember('vmpooler__migrating__' + pool['name'], vm['name'])
- pool_check_response[:discovered_vms] += 1
- $redis.sadd('vmpooler__discovered__' + pool['name'], vm['name'])
+ pool_check_response[:discovered_vms] += 1
+ redis.sadd('vmpooler__discovered__' + pool['name'], vm['name'])
- $logger.log('s', "[?] [#{pool['name']}] '#{vm['name']}' added to 'discovered' queue")
+ $logger.log('s', "[?] [#{pool['name']}] '#{vm['name']}' added to 'discovered' queue")
+ end
+
+ inventory[vm['name']] = 1
end
-
- inventory[vm['name']] = 1
end
end
rescue StandardError => e
@@ -1015,96 +1142,112 @@ module Vmpooler
end
def check_running_pool_vms(pool_name, provider, pool_check_response, inventory)
- $redis.smembers("vmpooler__running__#{pool_name}").each do |vm|
- if inventory[vm]
- begin
- vm_lifetime = $redis.hget('vmpooler__vm__' + vm, 'lifetime') || $config[:config]['vm_lifetime'] || 12
- pool_check_response[:checked_running_vms] += 1
- check_running_vm(vm, pool_name, vm_lifetime, provider)
- rescue StandardError => e
- $logger.log('d', "[!] [#{pool_name}] _check_pool with an error while evaluating running VMs: #{e}")
+ @redis.with_metrics do |redis|
+ redis.smembers("vmpooler__running__#{pool_name}").each do |vm|
+ if inventory[vm]
+ begin
+ vm_lifetime = redis.hget('vmpooler__vm__' + vm, 'lifetime') || $config[:config]['vm_lifetime'] || 12
+ pool_check_response[:checked_running_vms] += 1
+ check_running_vm(vm, pool_name, vm_lifetime, provider)
+ rescue StandardError => e
+ $logger.log('d', "[!] [#{pool_name}] _check_pool with an error while evaluating running VMs: #{e}")
+ end
+ else
+ move_vm_queue(pool_name, vm, 'running', 'completed', redis, 'is a running VM but is missing from inventory. Marking as completed.')
end
- else
- move_vm_queue(pool_name, vm, 'running', 'completed', 'is a running VM but is missing from inventory. Marking as completed.')
end
end
end
- def check_ready_pool_vms(pool_name, provider, pool_check_response, inventory, pool_ttl = 0)
- $redis.smembers("vmpooler__ready__#{pool_name}").each do |vm|
- if inventory[vm]
- begin
- pool_check_response[:checked_ready_vms] += 1
- check_ready_vm(vm, pool_name, pool_ttl || 0, provider)
- rescue StandardError => e
- $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating ready VMs: #{e}")
+ def check_ready_pool_vms(pool_name, provider, pool_check_response, inventory, pool_ttl)
+ @redis.with_metrics do |redis|
+ redis.smembers("vmpooler__ready__#{pool_name}").each do |vm|
+ if inventory[vm]
+ begin
+ pool_check_response[:checked_ready_vms] += 1
+ check_ready_vm(vm, pool_name, pool_ttl, provider)
+ rescue StandardError => e
+ $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating ready VMs: #{e}")
+ end
+ else
+ move_vm_queue(pool_name, vm, 'ready', 'completed', redis, 'is a ready VM but is missing from inventory. Marking as completed.')
end
- else
- move_vm_queue(pool_name, vm, 'ready', 'completed', 'is a ready VM but is missing from inventory. Marking as completed.')
end
end
end
- def check_pending_pool_vms(pool_name, provider, pool_check_response, inventory, pool_timeout = nil)
+ def check_pending_pool_vms(pool_name, provider, pool_check_response, inventory, pool_timeout)
pool_timeout ||= $config[:config]['timeout'] || 15
- $redis.smembers("vmpooler__pending__#{pool_name}").reverse.each do |vm|
- if inventory[vm]
- begin
- pool_check_response[:checked_pending_vms] += 1
- check_pending_vm(vm, pool_name, pool_timeout, provider)
- rescue StandardError => e
- $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating pending VMs: #{e}")
+ @redis.with_metrics do |redis|
+ redis.smembers("vmpooler__pending__#{pool_name}").reverse.each do |vm|
+ if inventory[vm]
+ begin
+ pool_check_response[:checked_pending_vms] += 1
+ check_pending_vm(vm, pool_name, pool_timeout, provider)
+ rescue StandardError => e
+ $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating pending VMs: #{e}")
+ end
+ else
+ fail_pending_vm(vm, pool_name, pool_timeout, redis, false)
end
- else
- fail_pending_vm(vm, pool_name, pool_timeout, false)
end
end
end
def check_completed_pool_vms(pool_name, provider, pool_check_response, inventory)
- $redis.smembers("vmpooler__completed__#{pool_name}").each do |vm|
- if inventory[vm]
- begin
- pool_check_response[:destroyed_vms] += 1
- destroy_vm(vm, pool_name, provider)
- rescue StandardError => e
- $redis.srem("vmpooler__completed__#{pool_name}", vm)
- $redis.hdel("vmpooler__active__#{pool_name}", vm)
- $redis.del("vmpooler__vm__#{vm}")
- $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating completed VMs: #{e}")
+ @redis.with_metrics do |redis|
+ redis.smembers("vmpooler__completed__#{pool_name}").each do |vm|
+ if inventory[vm]
+ begin
+ pool_check_response[:destroyed_vms] += 1
+ destroy_vm(vm, pool_name, provider)
+ rescue StandardError => e
+ redis.pipelined do
+ redis.srem("vmpooler__completed__#{pool_name}", vm)
+ redis.hdel("vmpooler__active__#{pool_name}", vm)
+ redis.del("vmpooler__vm__#{vm}")
+ end
+ $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating completed VMs: #{e}")
+ end
+ else
+ $logger.log('s', "[!] [#{pool_name}] '#{vm}' not found in inventory, removed from 'completed' queue")
+ redis.pipelined do
+ redis.srem("vmpooler__completed__#{pool_name}", vm)
+ redis.hdel("vmpooler__active__#{pool_name}", vm)
+ redis.del("vmpooler__vm__#{vm}")
+ end
end
- else
- $logger.log('s', "[!] [#{pool_name}] '#{vm}' not found in inventory, removed from 'completed' queue")
- $redis.srem("vmpooler__completed__#{pool_name}", vm)
- $redis.hdel("vmpooler__active__#{pool_name}", vm)
- $redis.del("vmpooler__vm__#{vm}")
end
end
end
def check_discovered_pool_vms(pool_name)
- $redis.smembers("vmpooler__discovered__#{pool_name}").reverse.each do |vm|
- %w[pending ready running completed].each do |queue|
- if $redis.sismember("vmpooler__#{queue}__#{pool_name}", vm)
- $logger.log('d', "[!] [#{pool_name}] '#{vm}' found in '#{queue}', removed from 'discovered' queue")
- $redis.srem("vmpooler__discovered__#{pool_name}", vm)
+ @redis.with_metrics do |redis|
+ redis.smembers("vmpooler__discovered__#{pool_name}").reverse.each do |vm|
+ %w[pending ready running completed].each do |queue|
+ if redis.sismember("vmpooler__#{queue}__#{pool_name}", vm)
+ $logger.log('d', "[!] [#{pool_name}] '#{vm}' found in '#{queue}', removed from 'discovered' queue")
+ redis.srem("vmpooler__discovered__#{pool_name}", vm)
+ end
end
- end
- $redis.smove("vmpooler__discovered__#{pool_name}", "vmpooler__completed__#{pool_name}", vm) if $redis.sismember("vmpooler__discovered__#{pool_name}", vm)
+ redis.smove("vmpooler__discovered__#{pool_name}", "vmpooler__completed__#{pool_name}", vm) if redis.sismember("vmpooler__discovered__#{pool_name}", vm)
+ end
end
rescue StandardError => e
$logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating discovered VMs: #{e}")
end
def check_migrating_pool_vms(pool_name, provider, pool_check_response, inventory)
- $redis.smembers("vmpooler__migrating__#{pool_name}").reverse.each do |vm|
- if inventory[vm]
- begin
- pool_check_response[:migrated_vms] += 1
- migrate_vm(vm, pool_name, provider)
- rescue StandardError => e
- $logger.log('s', "[x] [#{pool_name}] '#{vm}' failed to migrate: #{e}")
+ @redis.with_metrics do |redis|
+ redis.smembers("vmpooler__migrating__#{pool_name}").reverse.each do |vm|
+ if inventory[vm]
+ begin
+ pool_check_response[:migrated_vms] += 1
+ migrate_vm(vm, pool_name, provider)
+ rescue StandardError => e
+ $logger.log('s', "[x] [#{pool_name}] '#{vm}' failed to migrate: #{e}")
+ end
end
end
end
@@ -1113,29 +1256,37 @@ module Vmpooler
def repopulate_pool_vms(pool_name, provider, pool_check_response, pool_size)
return if pool_mutex(pool_name).locked?
- ready = $redis.scard("vmpooler__ready__#{pool_name}")
- total = $redis.scard("vmpooler__pending__#{pool_name}") + ready
+ @redis.with_metrics do |redis|
+ redis.multi
+ redis.scard("vmpooler__ready__#{pool_name}")
+ redis.scard("vmpooler__pending__#{pool_name}")
+ redis.scard("vmpooler__running__#{pool_name}")
+ ready, pending, running = redis.exec
+ total = pending.to_i + ready.to_i
- $metrics.gauge("ready.#{pool_name}", $redis.scard("vmpooler__ready__#{pool_name}"))
- $metrics.gauge("running.#{pool_name}", $redis.scard("vmpooler__running__#{pool_name}"))
+ $metrics.gauge("ready.#{pool_name}", ready)
+ $metrics.gauge("running.#{pool_name}", running)
- if $redis.get("vmpooler__empty__#{pool_name}")
- $redis.del("vmpooler__empty__#{pool_name}") unless ready == 0
- elsif ready == 0
- $redis.set("vmpooler__empty__#{pool_name}", 'true')
- $logger.log('s', "[!] [#{pool_name}] is empty")
- end
+ unless pool_size == 0
+ if redis.get("vmpooler__empty__#{pool_name}")
+ redis.del("vmpooler__empty__#{pool_name}") unless ready == 0
+ elsif ready == 0
+ redis.set("vmpooler__empty__#{pool_name}", 'true')
+ $logger.log('s', "[!] [#{pool_name}] is empty")
+ end
+ end
- (pool_size - total).times do
- if $redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
- begin
- $redis.incr('vmpooler__tasks__clone')
- pool_check_response[:cloned_vms] += 1
- clone_vm(pool_name, provider)
- rescue StandardError => e
- $logger.log('s', "[!] [#{pool_name}] clone failed during check_pool with an error: #{e}")
- $redis.decr('vmpooler__tasks__clone')
- raise
+ (pool_size - total.to_i).times do
+ if redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
+ begin
+ redis.incr('vmpooler__tasks__clone')
+ pool_check_response[:cloned_vms] += 1
+ clone_vm(pool_name, provider)
+ rescue StandardError => e
+ $logger.log('s', "[!] [#{pool_name}] clone failed during check_pool with an error: #{e}")
+ redis.decr('vmpooler__tasks__clone')
+ raise
+ end
end
end
end
@@ -1160,7 +1311,7 @@ module Vmpooler
check_running_pool_vms(pool['name'], provider, pool_check_response, inventory)
- check_ready_pool_vms(pool['name'], provider, pool_check_response, inventory, pool['ready_ttl'])
+ check_ready_pool_vms(pool['name'], provider, pool_check_response, inventory, pool['ready_ttl'] || $config[:config]['ready_ttl'])
check_pending_pool_vms(pool['name'], provider, pool_check_response, inventory, pool['timeout'])
@@ -1203,23 +1354,195 @@ module Vmpooler
#
# returns an object Vmpooler::PoolManager::Provider::*
# or raises an error if the class does not exist
- def create_provider_object(config, logger, metrics, provider_class, provider_name, options)
+ def create_provider_object(config, logger, metrics, redis_connection_pool, provider_class, provider_name, options)
provider_klass = Vmpooler::PoolManager::Provider
provider_klass.constants.each do |classname|
next unless classname.to_s.casecmp(provider_class) == 0
- return provider_klass.const_get(classname).new(config, logger, metrics, provider_name, options)
+ return provider_klass.const_get(classname).new(config, logger, metrics, redis_connection_pool, provider_name, options)
end
raise("Provider '#{provider_class}' is unknown for pool with provider name '#{provider_name}'") if provider.nil?
end
+ def check_ondemand_requests(maxloop = 0,
+ loop_delay_min = CHECK_LOOP_DELAY_MIN_DEFAULT,
+ loop_delay_max = CHECK_LOOP_DELAY_MAX_DEFAULT,
+ loop_delay_decay = CHECK_LOOP_DELAY_DECAY_DEFAULT)
+
+ $logger.log('d', '[*] [ondemand_provisioner] starting worker thread')
+
+ $threads['ondemand_provisioner'] = Thread.new do
+ _check_ondemand_requests(maxloop, loop_delay_min, loop_delay_max, loop_delay_decay)
+ end
+ end
+
+ def _check_ondemand_requests(maxloop = 0,
+ loop_delay_min = CHECK_LOOP_DELAY_MIN_DEFAULT,
+ loop_delay_max = CHECK_LOOP_DELAY_MAX_DEFAULT,
+ loop_delay_decay = CHECK_LOOP_DELAY_DECAY_DEFAULT)
+
+ loop_delay_min = $config[:config]['check_loop_delay_min'] unless $config[:config]['check_loop_delay_min'].nil?
+ loop_delay_max = $config[:config]['check_loop_delay_max'] unless $config[:config]['check_loop_delay_max'].nil?
+ loop_delay_decay = $config[:config]['check_loop_delay_decay'] unless $config[:config]['check_loop_delay_decay'].nil?
+
+ loop_delay_decay = 2.0 if loop_delay_decay <= 1.0
+ loop_delay_max = loop_delay_min if loop_delay_max.nil? || loop_delay_max < loop_delay_min
+
+ loop_count = 1
+ loop_delay = loop_delay_min
+
+ loop do
+ result = process_ondemand_requests
+
+ loop_delay = (loop_delay * loop_delay_decay).to_i
+ loop_delay = loop_delay_min if result > 0
+ loop_delay = loop_delay_max if loop_delay > loop_delay_max
+ sleep_with_wakeup_events(loop_delay, loop_delay_min, ondemand_request: true)
+
+ unless maxloop == 0
+ break if loop_count >= maxloop
+
+ loop_count += 1
+ end
+ end
+ end
+
+ def process_ondemand_requests
+ @redis.with_metrics do |redis|
+ requests = redis.zrange('vmpooler__provisioning__request', 0, -1)
+ requests&.map { |request_id| create_ondemand_vms(request_id, redis) }
+ provisioning_tasks = process_ondemand_vms(redis)
+ requests_ready = check_ondemand_requests_ready(redis)
+ requests.length + provisioning_tasks + requests_ready
+ end
+ end
+
+ def create_ondemand_vms(request_id, redis)
+ requested = redis.hget("vmpooler__odrequest__#{request_id}", 'requested')
+ unless requested
+ $logger.log('s', "Failed to find odrequest for request_id '#{request_id}'")
+ redis.zrem('vmpooler__provisioning__request', request_id)
+ return
+ end
+ score = redis.zscore('vmpooler__provisioning__request', request_id)
+ requested = requested.split(',')
+
+ redis.pipelined do
+ requested.each do |request|
+ redis.zadd('vmpooler__odcreate__task', Time.now.to_i, "#{request}:#{request_id}")
+ end
+ redis.zrem('vmpooler__provisioning__request', request_id)
+ redis.zadd('vmpooler__provisioning__processing', score, request_id)
+ end
+ end
+
+ def process_ondemand_vms(redis)
+ queue_key = 'vmpooler__odcreate__task'
+ queue = redis.zrange(queue_key, 0, -1, with_scores: true)
+ ondemand_clone_limit = $config[:config]['ondemand_clone_limit']
+ queue.each do |request, score|
+ clone_count = redis.get('vmpooler__tasks__ondemandclone').to_i
+ break unless clone_count < ondemand_clone_limit
+
+ pool_alias, pool, count, request_id = request.split(':')
+ count = count.to_i
+ provider = get_provider_for_pool(pool)
+ slots = ondemand_clone_limit - clone_count
+ break if slots == 0
+
+ if slots >= count
+ count.times do
+ redis.incr('vmpooler__tasks__ondemandclone')
+ clone_vm(pool, provider, request_id, pool_alias)
+ end
+ redis.zrem(queue_key, request)
+ else
+ remaining_count = count - slots
+ slots.times do
+ redis.incr('vmpooler__tasks__ondemandclone')
+ clone_vm(pool, provider, request_id, pool_alias)
+ end
+ redis.pipelined do
+ redis.zrem(queue_key, request)
+ redis.zadd(queue_key, score, "#{pool_alias}:#{pool}:#{remaining_count}:#{request_id}")
+ end
+ end
+ end
+ queue.length
+ end
+
+ def vms_ready?(request_id, redis)
+ catch :request_not_ready do
+ request_hash = redis.hgetall("vmpooler__odrequest__#{request_id}")
+ requested_platforms = request_hash['requested'].split(',')
+ requested_platforms.each do |platform|
+ platform_alias, pool, count = platform.split(':')
+ pools_filled = redis.scard("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
+ throw :request_not_ready unless pools_filled.to_i == count.to_i
+ end
+ return true
+ end
+ false
+ end
+
+ def check_ondemand_requests_ready(redis)
+ # default expiration is one month to ensure the data does not stay in redis forever
+ default_expiration = 259_200_0
+ in_progress_requests = redis.zrange('vmpooler__provisioning__processing', 0, -1, with_scores: true)
+ in_progress_requests&.each do |request_id, score|
+ next if request_expired?(request_id, score, redis)
+ next unless vms_ready?(request_id, redis)
+
+ redis.multi
+ redis.hset("vmpooler__odrequest__#{request_id}", 'status', 'ready')
+ redis.expire("vmpooler__odrequest__#{request_id}", default_expiration)
+ redis.zrem('vmpooler__provisioning__processing', request_id)
+ redis.exec
+ end
+ in_progress_requests.length
+ end
+
+ def request_expired?(request_id, score, redis)
+ delta = Time.now.to_i - score.to_i
+ ondemand_request_ttl = $config[:config]['ondemand_request_ttl']
+ return false unless delta > ondemand_request_ttl * 60
+
+ $logger.log('s', "Ondemand request for '#{request_id}' failed to provision all instances within the configured ttl '#{ondemand_request_ttl}'")
+ expiration_ttl = $config[:redis]['data_ttl'].to_i * 60 * 60
+ redis.pipelined do
+ redis.zrem('vmpooler__provisioning__processing', request_id)
+ redis.hset("vmpooler__odrequest__#{request_id}", 'status', 'failed')
+ redis.expire("vmpooler__odrequest__#{request_id}", expiration_ttl)
+ end
+ remove_vms_for_failed_request(request_id, expiration_ttl, redis)
+ true
+ end
+
+ def remove_vms_for_failed_request(request_id, expiration_ttl, redis)
+ request_hash = redis.hgetall("vmpooler__odrequest__#{request_id}")
+ requested_platforms = request_hash['requested'].split(',')
+ requested_platforms.each do |platform|
+ platform_alias, pool, _count = platform.split(':')
+ pools_filled = redis.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
+ redis.pipelined do
+ pools_filled&.each do |vm|
+ move_vm_queue(pool, vm, 'running', 'completed', redis, "moved to completed queue. '#{request_id}' could not be filled in time")
+ end
+ redis.expire("vmpooler__#{request_id}__#{platform_alias}__#{pool}", expiration_ttl)
+ end
+ end
+ end
+
def execute!(maxloop = 0, loop_delay = 1)
$logger.log('d', 'starting vmpooler')
- # Clear out the tasks manager, as we don't know about any tasks at this point
- $redis.set('vmpooler__tasks__clone', 0)
- # Clear out vmpooler__migrations since stale entries may be left after a restart
- $redis.del('vmpooler__migration')
+ @redis.with_metrics do |redis|
+ # Clear out the tasks manager, as we don't know about any tasks at this point
+ redis.set('vmpooler__tasks__clone', 0)
+ redis.set('vmpooler__tasks__ondemandclone', 0)
+ # Clear out vmpooler__migrations since stale entries may be left after a restart
+ redis.del('vmpooler__migration')
+ end
# Copy vSphere settings to correct location. This happens with older configuration files
if !$config[:vsphere].nil? && ($config[:providers].nil? || $config[:providers][:vsphere].nil?)
@@ -1269,7 +1592,7 @@ module Vmpooler
provider_class = $config[:providers][provider_name.to_sym]['provider_class']
end
begin
- $providers[provider_name] = create_provider_object($config, $logger, $metrics, provider_class, provider_name, {}) if $providers[provider_name].nil?
+ $providers[provider_name] = create_provider_object($config, $logger, $metrics, @redis, provider_class, provider_name, {}) if $providers[provider_name].nil?
rescue StandardError => e
$logger.log('s', "Error while creating provider for pool #{pool['name']}: #{e}")
raise
@@ -1303,6 +1626,13 @@ module Vmpooler
end
end
+ if !$threads['ondemand_provisioner']
+ check_ondemand_requests
+ elsif !$threads['ondemand_provisioner'].alive?
+ $logger.log('d', '[!] [ondemand_provisioner] worker thread died, restarting')
+ check_ondemand_requests(check_loop_delay_min, check_loop_delay_max, check_loop_delay_decay)
+ end
+
sleep(loop_delay)
unless maxloop == 0
diff --git a/lib/vmpooler/providers/base.rb b/lib/vmpooler/providers/base.rb
index c3c6d7d..635dfc7 100644
--- a/lib/vmpooler/providers/base.rb
+++ b/lib/vmpooler/providers/base.rb
@@ -14,10 +14,11 @@ module Vmpooler
# Provider options passed in during initialization
attr_reader :provider_options
- def initialize(config, logger, metrics, name, options)
+ def initialize(config, logger, metrics, redis_connection_pool, name, options)
@config = config
@logger = logger
@metrics = metrics
+ @redis = redis_connection_pool
@provider_name = name
# Ensure that there is not a nil provider configuration
diff --git a/lib/vmpooler/providers/dummy.rb b/lib/vmpooler/providers/dummy.rb
index 47eaf58..100bcb6 100644
--- a/lib/vmpooler/providers/dummy.rb
+++ b/lib/vmpooler/providers/dummy.rb
@@ -9,8 +9,8 @@ module Vmpooler
class Dummy < Vmpooler::PoolManager::Provider::Base
# Fake VM Provider for testing
- def initialize(config, logger, metrics, name, options)
- super(config, logger, metrics, name, options)
+ def initialize(config, logger, metrics, redis_connection_pool, name, options)
+ super(config, logger, metrics, redis_connection_pool, name, options)
dummyfilename = provider_config['filename']
# This initial_state option is only intended to be used by spec tests
diff --git a/lib/vmpooler/providers/vsphere.rb b/lib/vmpooler/providers/vsphere.rb
index 24b3b48..27ca3ac 100644
--- a/lib/vmpooler/providers/vsphere.rb
+++ b/lib/vmpooler/providers/vsphere.rb
@@ -9,8 +9,8 @@ module Vmpooler
# The connection_pool method is normally used only for testing
attr_reader :connection_pool
- def initialize(config, logger, metrics, name, options)
- super(config, logger, metrics, name, options)
+ def initialize(config, logger, metrics, redis_connection_pool, name, options)
+ super(config, logger, metrics, redis_connection_pool, name, options)
task_limit = global_config[:config].nil? || global_config[:config]['task_limit'].nil? ? 10 : global_config[:config]['task_limit'].to_i
# The default connection pool size is:
@@ -39,6 +39,7 @@ module Vmpooler
end
@provider_hosts = {}
@provider_hosts_lock = Mutex.new
+ @redis = redis_connection_pool
end
# name of the provider class
@@ -59,12 +60,16 @@ module Vmpooler
def destroy_vm_and_log(vm_name, vm_object, pool, data_ttl)
try = 0 if try.nil?
max_tries = 3
- $redis.srem("vmpooler__completed__#{pool}", vm_name)
- $redis.hdel("vmpooler__active__#{pool}", vm_name)
- $redis.hset("vmpooler__vm__#{vm_name}", 'destroy', Time.now)
+ @redis.with_metrics do |redis|
+ redis.multi
+ redis.srem("vmpooler__completed__#{pool}", vm_name)
+ redis.hdel("vmpooler__active__#{pool}", vm_name)
+ redis.hset("vmpooler__vm__#{vm_name}", 'destroy', Time.now)
- # Auto-expire metadata key
- $redis.expire('vmpooler__vm__' + vm_name, (data_ttl * 60 * 60))
+ # Auto-expire metadata key
+ redis.expire('vmpooler__vm__' + vm_name, (data_ttl * 60 * 60))
+ redis.exec
+ end
start = Time.now
@@ -968,22 +973,24 @@ module Vmpooler
begin
connection = ensured_vsphere_connection(pool_object)
vm_hash = get_vm_details(pool_name, vm_name, connection)
- $redis.hset("vmpooler__vm__#{vm_name}", 'host', vm_hash['host_name'])
- migration_limit = @config[:config]['migration_limit'] if @config[:config].key?('migration_limit')
- migration_count = $redis.scard('vmpooler__migration')
- if migration_enabled? @config
- if migration_count >= migration_limit
- logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}. No migration will be evaluated since the migration_limit has been reached")
- break
- end
- run_select_hosts(pool_name, @provider_hosts)
- if vm_in_target?(pool_name, vm_hash['host_name'], vm_hash['architecture'], @provider_hosts)
- logger.log('s', "[ ] [#{pool_name}] No migration required for '#{vm_name}' running on #{vm_hash['host_name']}")
+ @redis.with_metrics do |redis|
+ redis.hset("vmpooler__vm__#{vm_name}", 'host', vm_hash['host_name'])
+ migration_count = redis.scard('vmpooler__migration')
+ migration_limit = @config[:config]['migration_limit'] if @config[:config].key?('migration_limit')
+ if migration_enabled? @config
+ if migration_count >= migration_limit
+ logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}. No migration will be evaluated since the migration_limit has been reached")
+ break
+ end
+ run_select_hosts(pool_name, @provider_hosts)
+ if vm_in_target?(pool_name, vm_hash['host_name'], vm_hash['architecture'], @provider_hosts)
+ logger.log('s', "[ ] [#{pool_name}] No migration required for '#{vm_name}' running on #{vm_hash['host_name']}")
+ else
+ migrate_vm_to_new_host(pool_name, vm_name, vm_hash, connection)
+ end
else
- migrate_vm_to_new_host(pool_name, vm_name, vm_hash, connection)
+ logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}")
end
- else
- logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}")
end
rescue StandardError
logger.log('s', "[!] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}")
@@ -993,15 +1000,23 @@ module Vmpooler
end
def migrate_vm_to_new_host(pool_name, vm_name, vm_hash, connection)
- $redis.sadd('vmpooler__migration', vm_name)
+ @redis.with_metrics do |redis|
+ redis.sadd('vmpooler__migration', vm_name)
+ end
target_host_name = select_next_host(pool_name, @provider_hosts, vm_hash['architecture'])
target_host_object = find_host_by_dnsname(connection, target_host_name)
finish = migrate_vm_and_record_timing(pool_name, vm_name, vm_hash, target_host_object, target_host_name)
- $redis.hset("vmpooler__vm__#{vm_name}", 'host', target_host_name)
- $redis.hset("vmpooler__vm__#{vm_name}", 'migrated', true)
+ @redis.with_metrics do |redis|
+ redis.multi
+ redis.hset("vmpooler__vm__#{vm_name}", 'host', target_host_name)
+ redis.hset("vmpooler__vm__#{vm_name}", 'migrated', true)
+ redis.exec
+ end
logger.log('s', "[>] [#{pool_name}] '#{vm_name}' migrated from #{vm_hash['host_name']} to #{target_host_name} in #{finish} seconds")
ensure
- $redis.srem('vmpooler__migration', vm_name)
+ @redis.with_metrics do |redis|
+ redis.srem('vmpooler__migration', vm_name)
+ end
end
def migrate_vm_and_record_timing(pool_name, vm_name, vm_hash, target_host_object, dest_host_name)
@@ -1011,9 +1026,13 @@ module Vmpooler
metrics.timing("migrate.#{pool_name}", finish)
metrics.increment("migrate_from.#{vm_hash['host_name']}")
metrics.increment("migrate_to.#{dest_host_name}")
- checkout_to_migration = format('%.2f', time: Time.now - Time.parse($redis.hget("vmpooler__vm__#{vm_name}", 'checkout')))
- $redis.hset("vmpooler__vm__#{vm_name}", 'migration_time', finish)
- $redis.hset("vmpooler__vm__#{vm_name}", 'checkout_to_migration', checkout_to_migration)
+ @redis.with_metrics do |redis|
+ checkout_to_migration = format('%.2f', time: Time.now - Time.parse(redis.hget("vmpooler__vm__#{vm_name}", 'checkout')))
+ redis.multi
+ redis.hset("vmpooler__vm__#{vm_name}", 'migration_time', finish)
+ redis.hset("vmpooler__vm__#{vm_name}", 'checkout_to_migration', checkout_to_migration)
+ redis.exec
+ end
finish
end
diff --git a/spec/fixtures/vmpooler.yaml b/spec/fixtures/vmpooler.yaml
index 1e3e78f..c5c0522 100644
--- a/spec/fixtures/vmpooler.yaml
+++ b/spec/fixtures/vmpooler.yaml
@@ -36,6 +36,8 @@
- name: 'pool01'
size: 5
provider: dummy
+ ready_ttl: 5
- name: 'pool02'
size: 5
provider: dummy
+ ready_ttl: 5
diff --git a/spec/fixtures/vmpooler2.yaml b/spec/fixtures/vmpooler2.yaml
index 39a617a..a016d30 100644
--- a/spec/fixtures/vmpooler2.yaml
+++ b/spec/fixtures/vmpooler2.yaml
@@ -36,6 +36,8 @@
- name: 'pool03'
size: 5
provider: dummy
+ ready_ttl: 5
- name: 'pool04'
size: 5
provider: dummy
+ ready_ttl: 5
diff --git a/spec/helpers.rb b/spec/helpers.rb
index 589e00f..87245db 100644
--- a/spec/helpers.rb
+++ b/spec/helpers.rb
@@ -40,97 +40,115 @@ def token_exists?(token)
result && !result.empty?
end
-def create_ready_vm(template, name, token = nil)
- create_vm(name, token)
+def create_ready_vm(template, name, redis, token = nil)
+ create_vm(name, redis, token)
redis.sadd("vmpooler__ready__#{template}", name)
redis.hset("vmpooler__vm__#{name}", "template", template)
end
-def create_running_vm(template, name, token = nil, user = nil)
- create_vm(name, token, nil, user)
+def create_running_vm(template, name, redis, token = nil, user = nil)
+ create_vm(name, redis, token, user)
redis.sadd("vmpooler__running__#{template}", name)
redis.hset("vmpooler__vm__#{name}", 'template', template)
redis.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
redis.hset("vmpooler__vm__#{name}", 'host', 'host1')
end
-def create_pending_vm(template, name, token = nil)
- create_vm(name, token)
+def create_pending_vm(template, name, redis, token = nil)
+ create_vm(name, redis, token)
redis.sadd("vmpooler__pending__#{template}", name)
redis.hset("vmpooler__vm__#{name}", "template", template)
end
-def create_vm(name, token = nil, redis_handle = nil, user = nil)
- redis_db = redis_handle ? redis_handle : redis
- redis_db.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
- redis_db.hset("vmpooler__vm__#{name}", 'token:token', token) if token
- redis_db.hset("vmpooler__vm__#{name}", 'token:user', user) if user
+def create_vm(name, redis, token = nil, user = nil)
+ redis.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
+ redis.hset("vmpooler__vm__#{name}", 'clone', Time.now)
+ redis.hset("vmpooler__vm__#{name}", 'token:token', token) if token
+ redis.hset("vmpooler__vm__#{name}", 'token:user', user) if user
end
-def create_completed_vm(name, pool, active = false, redis_handle = nil)
- redis_db = redis_handle ? redis_handle : redis
- redis_db.sadd("vmpooler__completed__#{pool}", name)
- redis_db.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
- redis_db.hset("vmpooler__active__#{pool}", name, Time.now) if active
+def create_completed_vm(name, pool, redis, active = false)
+ redis.sadd("vmpooler__completed__#{pool}", name)
+ redis.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
+ redis.hset("vmpooler__active__#{pool}", name, Time.now) if active
end
-def create_discovered_vm(name, pool, redis_handle = nil)
- redis_db = redis_handle ? redis_handle : redis
- redis_db.sadd("vmpooler__discovered__#{pool}", name)
+def create_discovered_vm(name, pool, redis)
+ redis.sadd("vmpooler__discovered__#{pool}", name)
end
-def create_migrating_vm(name, pool, redis_handle = nil)
- redis_db = redis_handle ? redis_handle : redis
- redis_db.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
- redis_db.sadd("vmpooler__migrating__#{pool}", name)
+def create_migrating_vm(name, pool, redis)
+ redis.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
+ redis.sadd("vmpooler__migrating__#{pool}", name)
end
-def create_tag(vm, tag_name, tag_value, redis_handle = nil)
- redis_db = redis_handle ? redis-handle : redis
- redis_db.hset("vmpooler__vm__#{vm}", "tag:#{tag_name}", tag_value)
+def create_tag(vm, tag_name, tag_value, redis)
+ redis.hset("vmpooler__vm__#{vm}", "tag:#{tag_name}", tag_value)
end
-def add_vm_to_migration_set(name, redis_handle = nil)
- redis_db = redis_handle ? redis_handle : redis
- redis_db.sadd('vmpooler__migration', name)
+def add_vm_to_migration_set(name, redis)
+ redis.sadd('vmpooler__migration', name)
end
def fetch_vm(vm)
redis.hgetall("vmpooler__vm__#{vm}")
end
-def set_vm_data(vm, key, value)
+def set_vm_data(vm, key, value, redis)
redis.hset("vmpooler__vm__#{vm}", key, value)
end
-def snapshot_revert_vm(vm, snapshot = '12345678901234567890123456789012')
+def snapshot_revert_vm(vm, snapshot = '12345678901234567890123456789012', redis)
redis.sadd('vmpooler__tasks__snapshot-revert', "#{vm}:#{snapshot}")
redis.hset("vmpooler__vm__#{vm}", "snapshot:#{snapshot}", "1")
end
-def snapshot_vm(vm, snapshot = '12345678901234567890123456789012')
+def snapshot_vm(vm, snapshot = '12345678901234567890123456789012', redis)
redis.sadd('vmpooler__tasks__snapshot', "#{vm}:#{snapshot}")
redis.hset("vmpooler__vm__#{vm}", "snapshot:#{snapshot}", "1")
end
-def disk_task_vm(vm, disk_size = '10')
+def disk_task_vm(vm, disk_size = '10', redis)
redis.sadd('vmpooler__tasks__disk', "#{vm}:#{disk_size}")
end
-def has_vm_snapshot?(vm)
+def has_vm_snapshot?(vm, redis)
redis.smembers('vmpooler__tasks__snapshot').any? do |snapshot|
- instance, sha = snapshot.split(':')
+ instance, _sha = snapshot.split(':')
vm == instance
end
end
-def vm_reverted_to_snapshot?(vm, snapshot = nil)
+def vm_reverted_to_snapshot?(vm, redis, snapshot = nil)
redis.smembers('vmpooler__tasks__snapshot-revert').any? do |action|
instance, sha = action.split(':')
instance == vm and (snapshot ? (sha == snapshot) : true)
end
end
-def pool_has_ready_vm?(pool, vm)
+def pool_has_ready_vm?(pool, vm, redis)
!!redis.sismember('vmpooler__ready__' + pool, vm)
end
+
+def create_ondemand_request_for_test(request_id, score, platforms_string, redis, user = nil, token = nil)
+ redis.zadd('vmpooler__provisioning__request', score, request_id)
+ redis.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
+ redis.hset("vmpooler__odrequest__#{request_id}", 'token:token', token) if token
+ redis.hset("vmpooler__odrequest__#{request_id}", 'token:user', user) if user
+end
+
+def set_ondemand_request_status(request_id, status, redis)
+ redis.hset("vmpooler__odrequest__#{request_id}", 'status', status)
+end
+
+def create_ondemand_vm(vmname, request_id, pool, pool_alias, redis)
+ redis.sadd("vmpooler__#{request_id}__#{pool_alias}__#{pool}", vmname)
+end
+
+def create_ondemand_creationtask(request_string, score, redis)
+ redis.zadd('vmpooler__odcreate__task', score, request_string)
+end
+
+def create_ondemand_processing(request_id, score, redis)
+ redis.zadd('vmpooler__provisioning__processing', score, request_id)
+end
diff --git a/spec/integration/api/v1/config_spec.rb b/spec/integration/api/v1/config_spec.rb
index 0b73d54..91008a4 100644
--- a/spec/integration/api/v1/config_spec.rb
+++ b/spec/integration/api/v1/config_spec.rb
@@ -160,7 +160,7 @@ describe Vmpooler::API::V1 do
expect_json(ok = false, http = 400)
expected = {
ok: false,
- bad_templates: ['pool10']
+ not_configured: ['pool10']
}
expect(last_response.body).to eq(JSON.pretty_generate(expected))
@@ -190,7 +190,7 @@ describe Vmpooler::API::V1 do
expected = {
ok: false,
- bad_templates: ['pool1']
+ not_configured: ['pool1']
}
expect(last_response.body).to eq(JSON.pretty_generate(expected))
@@ -202,7 +202,7 @@ describe Vmpooler::API::V1 do
expected = {
ok: false,
- bad_templates: ['pool1']
+ not_configured: ['pool1']
}
expect(last_response.body).to eq(JSON.pretty_generate(expected))
diff --git a/spec/integration/api/v1/ondemandvm_spec.rb b/spec/integration/api/v1/ondemandvm_spec.rb
new file mode 100644
index 0000000..0665973
--- /dev/null
+++ b/spec/integration/api/v1/ondemandvm_spec.rb
@@ -0,0 +1,362 @@
+require 'spec_helper'
+require 'rack/test'
+
+describe Vmpooler::API::V1 do
+ include Rack::Test::Methods
+
+ def app()
+ Vmpooler::API end
+
+ describe '/ondemandvm' do
+ let(:prefix) { '/api/v1' }
+ let(:metrics) { Vmpooler::DummyStatsd.new }
+ let(:config) {
+ {
+ config: {
+ 'site_name' => 'test pooler',
+ 'vm_lifetime_auth' => 2,
+ 'max_ondemand_instances_per_request' => 50,
+ 'backend_weight' => {
+ 'compute1' => 5
+ }
+ },
+ pools: [
+ {'name' => 'pool1', 'size' => 0},
+ {'name' => 'pool2', 'size' => 0, 'clone_target' => 'compute1'},
+ {'name' => 'pool3', 'size' => 0, 'clone_target' => 'compute1'}
+ ],
+ alias: { 'poolone' => ['pool1'] },
+ pool_names: [ 'pool1', 'pool2', 'pool3', 'poolone' ]
+ }
+ }
+ let(:current_time) { Time.now }
+ let(:vmname) { 'abcdefghijkl' }
+ let(:checkoutlock) { Mutex.new }
+ let(:redis) { MockRedis.new }
+ let(:uuid) { SecureRandom.uuid }
+
+ before(:each) do
+ app.settings.set :config, config
+ app.settings.set :redis, redis
+ app.settings.set :metrics, metrics
+ 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 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/status_spec.rb b/spec/integration/api/v1/status_spec.rb
index ffd69aa..10f127f 100644
--- a/spec/integration/api/v1/status_spec.rb
+++ b/spec/integration/api/v1/status_spec.rb
@@ -50,7 +50,7 @@ describe Vmpooler::API::V1 do
end
it 'returns the number of ready vms for each pool' do
- 3.times {|i| create_ready_vm("pool1", "vm-#{i}") }
+ 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...
@@ -61,8 +61,8 @@ describe Vmpooler::API::V1 do
end
it 'returns the number of running vms for each pool' do
- 3.times {|i| create_running_vm("pool1", "vm-#{i}") }
- 4.times {|i| create_running_vm("pool2", "vm-#{i}") }
+ 3.times {|i| create_running_vm("pool1", "vm-#{i}", redis) }
+ 4.times {|i| create_running_vm("pool2", "vm-#{i}", redis) }
get "#{prefix}/status/"
@@ -74,8 +74,8 @@ describe Vmpooler::API::V1 do
end
it 'returns the number of pending vms for each pool' do
- 3.times {|i| create_pending_vm("pool1", "vm-#{i}") }
- 4.times {|i| create_pending_vm("pool2", "vm-#{i}") }
+ 3.times {|i| create_pending_vm("pool1", "vm-#{i}", redis) }
+ 4.times {|i| create_pending_vm("pool2", "vm-#{i}", redis) }
get "#{prefix}/status/"
@@ -230,8 +230,8 @@ describe Vmpooler::API::V1 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}") }
- 5.times {|i| create_running_vm("pool3", "vm-#{i}") }
+ 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
diff --git a/spec/integration/api/v1/vm_hostname_spec.rb b/spec/integration/api/v1/vm_hostname_spec.rb
index 973163e..bca9866 100644
--- a/spec/integration/api/v1/vm_hostname_spec.rb
+++ b/spec/integration/api/v1/vm_hostname_spec.rb
@@ -33,6 +33,8 @@ describe Vmpooler::API::V1 do
let(:current_time) { Time.now }
+ let(:redis) { MockRedis.new }
+
before(:each) do
app.settings.set :config, config
app.settings.set :redis, redis
@@ -41,7 +43,7 @@ describe Vmpooler::API::V1 do
describe 'PUT /vm/:hostname' do
it 'allows tags to be set' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"tags":{"tested_by":"rspec"}}'
expect_json(ok = true, http = 200)
@@ -49,7 +51,7 @@ describe Vmpooler::API::V1 do
end
it 'skips empty tags' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"tags":{"tested_by":""}}'
expect_json(ok = true, http = 200)
@@ -57,7 +59,7 @@ describe Vmpooler::API::V1 do
end
it 'does not set tags if request body format is invalid' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"tags":{"tested"}}'
expect_json(ok = false, http = 400)
@@ -69,7 +71,7 @@ describe Vmpooler::API::V1 do
app.settings.set :config,
{ :config => { 'allowed_tags' => ['created_by', 'project', 'url'] } }
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"tags":{"created_by":"rspec","tested_by":"rspec"}}'
expect_json(ok = false, http = 400)
@@ -84,7 +86,7 @@ describe Vmpooler::API::V1 do
} }
it 'correctly filters tags' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"tags":{"url":"foo.com/something.html"}}'
expect_json(ok = true, http = 200)
@@ -93,7 +95,7 @@ describe Vmpooler::API::V1 do
end
it "doesn't eat tags not matching filter" do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"tags":{"url":"foo.com"}}'
expect_json(ok = true, http = 200)
@@ -105,7 +107,7 @@ describe Vmpooler::API::V1 do
let(:config) { { auth: false } }
it 'allows VM lifetime to be modified without a token' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"lifetime":"1"}'
expect_json(ok = true, http = 200)
@@ -115,7 +117,7 @@ describe Vmpooler::API::V1 do
end
it 'does not allow a lifetime to be 0' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"lifetime":"0"}'
expect_json(ok = false, http = 400)
@@ -125,7 +127,7 @@ describe Vmpooler::API::V1 do
end
it 'does not enforce a lifetime' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"lifetime":"20000"}'
expect_json(ok = true, http = 200)
@@ -137,7 +139,7 @@ describe Vmpooler::API::V1 do
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')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"lifetime":"200"}'
expect_json(ok = false, http = 400)
@@ -146,6 +148,19 @@ describe Vmpooler::API::V1 do
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
@@ -154,7 +169,7 @@ describe Vmpooler::API::V1 do
end
it 'allows VM lifetime to be modified with a token' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"lifetime":"1"}', {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
@@ -166,7 +181,7 @@ describe Vmpooler::API::V1 do
end
it 'does not allows VM lifetime to be modified without a token' do
- create_vm('testhost')
+ create_vm('testhost', redis)
put "#{prefix}/vm/testhost", '{"lifetime":"1"}'
expect_json(ok = false, http = 401)
@@ -182,7 +197,7 @@ describe Vmpooler::API::V1 do
end
it 'deletes an existing VM' do
- create_running_vm('pool1', 'testhost')
+ create_running_vm('pool1', 'testhost', redis)
expect fetch_vm('testhost')
delete "#{prefix}/vm/testhost"
@@ -198,7 +213,7 @@ describe Vmpooler::API::V1 do
context '(checked-out without token)' do
it 'deletes a VM without supplying a token' do
- create_running_vm('pool1', 'testhost')
+ create_running_vm('pool1', 'testhost', redis)
expect fetch_vm('testhost')
delete "#{prefix}/vm/testhost"
@@ -209,7 +224,7 @@ describe Vmpooler::API::V1 do
context '(checked-out with token)' do
it 'fails to delete a VM without supplying a token' do
- create_running_vm('pool1', 'testhost', 'abcdefghijklmnopqrstuvwxyz012345')
+ create_running_vm('pool1', 'testhost', redis, 'abcdefghijklmnopqrstuvwxyz012345')
expect fetch_vm('testhost')
delete "#{prefix}/vm/testhost"
@@ -218,7 +233,7 @@ describe Vmpooler::API::V1 do
end
it 'deletes a VM when token is supplied' do
- create_running_vm('pool1', 'testhost', 'abcdefghijklmnopqrstuvwxyz012345')
+ create_running_vm('pool1', 'testhost', redis, 'abcdefghijklmnopqrstuvwxyz012345')
expect fetch_vm('testhost')
delete "#{prefix}/vm/testhost", "", {
@@ -235,7 +250,7 @@ describe Vmpooler::API::V1 do
describe 'POST /vm/:hostname/snapshot' do
context '(auth not configured)' do
it 'creates a snapshot' do
- create_vm('testhost')
+ 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)
@@ -250,19 +265,19 @@ describe Vmpooler::API::V1 do
it 'returns a 401 if not authed' do
post "#{prefix}/vm/testhost/snapshot"
expect_json(ok = false, http = 401)
- expect !has_vm_snapshot?('testhost')
+ expect !has_vm_snapshot?('testhost', redis)
end
it 'creates a snapshot if authed' do
- create_vm('testhost')
- snapshot_vm('testhost', 'testsnapshot')
+ 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')
+ expect has_vm_snapshot?('testhost', redis)
end
end
end
@@ -270,22 +285,22 @@ describe Vmpooler::API::V1 do
describe 'POST /vm/:hostname/snapshot/:snapshot' do
context '(auth not configured)' do
it 'reverts to a snapshot' do
- create_vm('testhost')
- snapshot_vm('testhost', 'testsnapshot')
+ 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', 'testsnapshot')
+ expect vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot')
end
it 'fails if the specified snapshot does not exist' do
- create_vm('testhost')
+ 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', 'testsnapshot')
+ expect !vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot')
end
end
@@ -295,33 +310,33 @@ describe Vmpooler::API::V1 do
end
it 'returns a 401 if not authed' do
- create_vm('testhost')
- snapshot_vm('testhost', 'testsnapshot')
+ 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', 'testsnapshot')
+ expect !vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot')
end
it 'fails if authed and the specified snapshot does not exist' do
- create_vm('testhost')
+ 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', 'testsnapshot')
+ expect !vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot')
end
it 'reverts to a snapshot if authed' do
- create_vm('testhost')
- snapshot_vm('testhost', 'testsnapshot')
+ 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', 'testsnapshot')
+ expect vm_reverted_to_snapshot?('testhost', redis, 'testsnapshot')
end
end
end
diff --git a/spec/integration/api/v1/vm_spec.rb b/spec/integration/api/v1/vm_spec.rb
index 78be432..54af246 100644
--- a/spec/integration/api/v1/vm_spec.rb
+++ b/spec/integration/api/v1/vm_spec.rb
@@ -42,7 +42,7 @@ describe Vmpooler::API::V1 do
describe 'GET /vm/:hostname' do
it 'returns correct information on a running vm' do
- create_running_vm 'pool1', vmname
+ create_running_vm 'pool1', vmname, redis
get "#{prefix}/vm/#{vmname}"
expect_json(ok = true, http = 200)
response_body = (JSON.parse(last_response.body)[vmname])
@@ -63,7 +63,7 @@ describe Vmpooler::API::V1 do
let(:socket) { double('socket') }
it 'returns a single VM' do
- create_ready_vm 'pool1', vmname
+ 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"}'
@@ -80,7 +80,7 @@ describe Vmpooler::API::V1 do
end
it 'returns a single VM for an alias' do
- create_ready_vm 'pool1', vmname
+ create_ready_vm 'pool1', vmname, redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -106,7 +106,7 @@ describe Vmpooler::API::V1 do
Vmpooler::API.settings.config.delete(:alias)
Vmpooler::API.settings.config[:pool_names] = ['pool1', 'pool2']
- create_ready_vm 'pool1', vmname
+ create_ready_vm 'pool1', vmname, redis
post "#{prefix}/vm/pool1"
post "#{prefix}/vm/pool1"
@@ -117,7 +117,7 @@ describe Vmpooler::API::V1 do
end
it 'returns 503 for empty pool referenced by alias' do
- create_ready_vm 'pool1', vmname
+ create_ready_vm 'pool1', vmname, redis
post "#{prefix}/vm/poolone"
post "#{prefix}/vm/poolone"
@@ -128,8 +128,8 @@ describe Vmpooler::API::V1 do
end
it 'returns multiple VMs' do
- create_ready_vm 'pool1', vmname
- create_ready_vm 'pool2', 'qrstuvwxyz012345'
+ 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)
@@ -150,9 +150,9 @@ describe Vmpooler::API::V1 do
end
it 'returns multiple VMs even when multiple instances from the same pool are requested' do
- create_ready_vm 'pool1', '1abcdefghijklmnop'
- create_ready_vm 'pool1', '2abcdefghijklmnop'
- create_ready_vm 'pool2', 'qrstuvwxyz012345'
+ 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)
@@ -177,11 +177,11 @@ describe Vmpooler::API::V1 do
end
it 'returns multiple VMs even when multiple instances from multiple pools are requested' do
- create_ready_vm 'pool1', '1abcdefghijklmnop'
- create_ready_vm 'pool1', '2abcdefghijklmnop'
- create_ready_vm 'pool2', '1qrstuvwxyz012345'
- create_ready_vm 'pool2', '2qrstuvwxyz012345'
- create_ready_vm 'pool2', '3qrstuvwxyz012345'
+ 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)
@@ -208,9 +208,9 @@ describe Vmpooler::API::V1 do
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'
- create_ready_vm 'pool2', '2abcdefghijklmnop'
- create_ready_vm 'pool3', '1qrstuvwxyz012345'
+ 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)
@@ -231,9 +231,9 @@ describe Vmpooler::API::V1 do
end
it 'returns the first VM that was moved to the ready state when checking out a VM' do
- create_ready_vm 'pool1', '1abcdefghijklmnop'
- create_ready_vm 'pool1', '2abcdefghijklmnop'
- create_ready_vm 'pool1', '3abcdefghijklmnop'
+ 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)
@@ -251,7 +251,7 @@ describe Vmpooler::API::V1 do
end
it 'fails when not all requested vms can be allocated' do
- create_ready_vm 'pool1', '1abcdefghijklmnop'
+ create_ready_vm 'pool1', '1abcdefghijklmnop', redis
post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}'
@@ -262,7 +262,7 @@ describe Vmpooler::API::V1 do
end
it 'returns any checked out vms to their pools when not all requested vms can be allocated' do
- create_ready_vm 'pool1', '1abcdefghijklmnop'
+ create_ready_vm 'pool1', '1abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -273,11 +273,11 @@ describe Vmpooler::API::V1 do
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
- expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop')).to eq(true)
+ 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'
+ create_ready_vm 'pool1', '1abcdefghijklmnop', redis
post "#{prefix}/vm", '{"pool1":"2","pool2":"1"}'
@@ -288,7 +288,7 @@ describe Vmpooler::API::V1 do
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'
+ create_ready_vm 'pool1', '1abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -299,11 +299,11 @@ describe Vmpooler::API::V1 do
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
- expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop')).to eq(true)
+ 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'
+ create_ready_vm 'pool1', '1abcdefghijklmnop', redis
post "#{prefix}/vm", '{"pool1":"2","pool2":"3"}'
@@ -314,8 +314,8 @@ describe Vmpooler::API::V1 do
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'
- create_ready_vm 'pool1', '2abcdefghijklmnop'
+ 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)
@@ -326,13 +326,13 @@ describe Vmpooler::API::V1 do
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
- expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop')).to eq(true)
- expect(pool_has_ready_vm?('pool1', '2abcdefghijklmnop')).to eq(true)
+ 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
- create_ready_vm 'pool1', "2#{vmname}"
+ 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)
@@ -349,14 +349,14 @@ describe Vmpooler::API::V1 do
expect(last_response.body).to eq(JSON.pretty_generate(expected))
- expect(pool_has_ready_vm?('pool1', vmname)).to be false
+ 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'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -382,7 +382,7 @@ describe Vmpooler::API::V1 do
it 'extends VM lifetime if auth token is provided' do
app.settings.set :config, auth: true
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -405,7 +405,7 @@ describe Vmpooler::API::V1 do
it 'does not extend VM lifetime if auth token is not provided' do
app.settings.set :config, auth: true
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
diff --git a/spec/integration/api/v1/vm_template_spec.rb b/spec/integration/api/v1/vm_template_spec.rb
index 9620f1f..043c8e5 100644
--- a/spec/integration/api/v1/vm_template_spec.rb
+++ b/spec/integration/api/v1/vm_template_spec.rb
@@ -19,7 +19,8 @@ describe Vmpooler::API::V1 do
},
pools: [
{'name' => 'pool1', 'size' => 5},
- {'name' => 'pool2', 'size' => 10}
+ {'name' => 'pool2', 'size' => 10},
+ {'name' => 'poolone', 'size' => 0}
],
statsd: { 'prefix' => 'stats_prefix'},
alias: { 'poolone' => 'pool1' },
@@ -42,7 +43,7 @@ describe Vmpooler::API::V1 do
describe 'POST /vm/:template' do
it 'returns a single VM' do
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -60,7 +61,7 @@ describe Vmpooler::API::V1 do
end
it 'returns a single VM for an alias' do
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -86,7 +87,7 @@ describe Vmpooler::API::V1 do
Vmpooler::API.settings.config.delete(:alias)
Vmpooler::API.settings.config[:pool_names] = ['pool1', 'pool2']
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
post "#{prefix}/vm/pool1"
post "#{prefix}/vm/pool1"
@@ -97,8 +98,7 @@ describe Vmpooler::API::V1 do
end
it 'returns 503 for empty pool referenced by alias' do
- create_ready_vm 'pool1', 'abcdefghijklmnop'
- post "#{prefix}/vm/poolone"
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
post "#{prefix}/vm/poolone"
expected = { ok: false }
@@ -108,8 +108,8 @@ describe Vmpooler::API::V1 do
end
it 'returns multiple VMs' do
- create_ready_vm 'pool1', 'abcdefghijklmnop'
- create_ready_vm 'pool2', 'qrstuvwxyz012345'
+ 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)
@@ -130,12 +130,12 @@ describe Vmpooler::API::V1 do
end
it 'returns multiple VMs even when multiple instances from multiple pools are requested' do
- create_ready_vm 'pool1', '1abcdefghijklmnop'
- create_ready_vm 'pool1', '2abcdefghijklmnop'
+ create_ready_vm 'pool1', '1abcdefghijklmnop', redis
+ create_ready_vm 'pool1', '2abcdefghijklmnop', redis
- create_ready_vm 'pool2', '1qrstuvwxyz012345'
- create_ready_vm 'pool2', '2qrstuvwxyz012345'
- create_ready_vm 'pool2', '3qrstuvwxyz012345'
+ 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)
@@ -159,7 +159,7 @@ describe Vmpooler::API::V1 do
end
it 'fails when not all requested vms can be allocated' do
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
post "#{prefix}/vm/pool1+pool2", ''
@@ -170,7 +170,7 @@ describe Vmpooler::API::V1 do
end
it 'returns any checked out vms to their pools when not all requested vms can be allocated' do
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -181,12 +181,12 @@ describe Vmpooler::API::V1 do
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
- expect(pool_has_ready_vm?('pool1', 'abcdefghijklmnop')).to eq(true)
+ 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'
- create_ready_vm 'pool1', '0123456789012345'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
+ create_ready_vm 'pool1', '0123456789012345', redis
post "#{prefix}/vm/pool1+pool1+pool2", ''
@@ -197,8 +197,8 @@ describe Vmpooler::API::V1 do
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'
- create_ready_vm 'pool1', '0123456789012345'
+ 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)
@@ -209,13 +209,13 @@ describe Vmpooler::API::V1 do
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
- expect(pool_has_ready_vm?('pool1', 'abcdefghijklmnop')).to eq(true)
- expect(pool_has_ready_vm?('pool1', '0123456789012345')).to eq(true)
+ 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'
- create_ready_vm 'pool2', '0123456789012345'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
+ create_ready_vm 'pool2', '0123456789012345', redis
post "#{prefix}/vm/pool1+pool1+pool2+pool2+pool2", ''
@@ -226,8 +226,8 @@ describe Vmpooler::API::V1 do
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'
- create_ready_vm 'pool2', '0123456789012345'
+ 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)
@@ -238,15 +238,15 @@ describe Vmpooler::API::V1 do
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
- expect(pool_has_ready_vm?('pool1', 'abcdefghijklmnop')).to eq(true)
- expect(pool_has_ready_vm?('pool2', '0123456789012345')).to eq(true)
+ 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'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -272,7 +272,7 @@ describe Vmpooler::API::V1 do
it 'extends VM lifetime if auth token is provided' do
app.settings.set :config, auth: true
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
@@ -295,7 +295,7 @@ describe Vmpooler::API::V1 do
it 'does not extend VM lifetime if auth token is not provided' do
app.settings.set :config, auth: true
- create_ready_vm 'pool1', 'abcdefghijklmnop'
+ create_ready_vm 'pool1', 'abcdefghijklmnop', redis
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
diff --git a/spec/integration/dashboard_spec.rb b/spec/integration/dashboard_spec.rb
index 5bb9ac2..9d5a64a 100644
--- a/spec/integration/dashboard_spec.rb
+++ b/spec/integration/dashboard_spec.rb
@@ -56,11 +56,11 @@ describe Vmpooler::API do
context 'without history param' do
it 'returns basic JSON' do
- create_ready_vm('pool1', 'vm1')
- create_ready_vm('pool1', 'vm2')
- create_ready_vm('pool1', 'vm3')
- create_ready_vm('pool2', 'vm4')
- create_ready_vm('pool2', 'vm5')
+ create_ready_vm('pool1', 'vm1', redis)
+ create_ready_vm('pool1', 'vm2', redis)
+ create_ready_vm('pool1', 'vm3', redis)
+ create_ready_vm('pool2', 'vm4', redis)
+ create_ready_vm('pool2', 'vm5', redis)
get '/dashboard/stats/vmpooler/pool'
@@ -90,11 +90,11 @@ describe Vmpooler::API do
end
it 'returns JSON with history when redis has values' do
- create_ready_vm('pool1', 'vm1')
- create_ready_vm('pool1', 'vm2')
- create_ready_vm('pool1', 'vm3')
- create_ready_vm('pool2', 'vm4')
- create_ready_vm('pool2', 'vm5')
+ create_ready_vm('pool1', 'vm1', redis)
+ create_ready_vm('pool1', 'vm2', redis)
+ create_ready_vm('pool1', 'vm3', redis)
+ create_ready_vm('pool2', 'vm4', redis)
+ create_ready_vm('pool2', 'vm5', redis)
get '/dashboard/stats/vmpooler/pool', :history => true
@@ -140,18 +140,18 @@ describe Vmpooler::API do
end
it 'adds major correctly' do
- create_running_vm('pool-1', 'vm1')
- create_running_vm('pool-1', 'vm2')
- create_running_vm('pool-1', 'vm3')
+ create_running_vm('pool-1', 'vm1', redis)
+ create_running_vm('pool-1', 'vm2', redis)
+ create_running_vm('pool-1', 'vm3', redis)
- create_running_vm('pool-2', 'vm4')
- create_running_vm('pool-2', 'vm5')
- create_running_vm('pool-2', 'vm6')
- create_running_vm('pool-2', 'vm7')
- create_running_vm('pool-2', 'vm8')
+ create_running_vm('pool-2', 'vm4', redis)
+ create_running_vm('pool-2', 'vm5', redis)
+ create_running_vm('pool-2', 'vm6', redis)
+ create_running_vm('pool-2', 'vm7', redis)
+ create_running_vm('pool-2', 'vm8', redis)
- create_running_vm('diffpool-1', 'vm9')
- create_running_vm('diffpool-1', 'vm10')
+ create_running_vm('diffpool-1', 'vm9', redis)
+ create_running_vm('diffpool-1', 'vm10', redis)
get '/dashboard/stats/vmpooler/running'
diff --git a/spec/unit/api/helpers_spec.rb b/spec/unit/api/helpers_spec.rb
index 62fed34..e6ce9a7 100644
--- a/spec/unit/api/helpers_spec.rb
+++ b/spec/unit/api/helpers_spec.rb
@@ -128,7 +128,7 @@ describe Vmpooler::API::Helpers do
]
allow(redis).to receive(:pipelined).with(no_args).and_return [1,1]
- allow(redis).to receive(:get).and_return 1
+ allow(redis).to receive(:get).and_return(1,0)
expect(subject.get_queue_metrics(pools, redis)).to eq({pending: 2, cloning: 1, booting: 1, ready: 2, running: 2, completed: 2, total: 8})
end
@@ -140,7 +140,7 @@ describe Vmpooler::API::Helpers do
]
allow(redis).to receive(:pipelined).with(no_args).and_return [1,1]
- allow(redis).to receive(:get).and_return 5
+ allow(redis).to receive(:get).and_return(5,0)
expect(subject.get_queue_metrics(pools, redis)).to eq({pending: 2, cloning: 5, booting: 0, ready: 2, running: 2, completed: 2, total: 8})
end
diff --git a/spec/unit/pool_manager_spec.rb b/spec/unit/pool_manager_spec.rb
index e3218f6..03d4f76 100644
--- a/spec/unit/pool_manager_spec.rb
+++ b/spec/unit/pool_manager_spec.rb
@@ -8,25 +8,35 @@ require 'mock_redis'
RSpec::Matchers.define :a_pool_with_name_of do |value|
match { |actual| actual['name'] == value }
end
-
describe 'Pool Manager' do
let(:logger) { MockLogger.new }
- let(:redis) { MockRedis.new }
let(:metrics) { Vmpooler::DummyStatsd.new }
let(:pool) { 'pool1' }
let(:vm) { 'vm1' }
let(:timeout) { 5 }
let(:host) { double('host') }
- let(:token) { 'token1234'}
+ let(:token) { 'token1234' }
+ let(:request_id) { '1234' }
+ let(:current_time) { Time.now }
let(:provider_options) { {} }
- let(:provider) { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, 'mock_provider', provider_options) }
+ let(:redis_connection_pool) { Vmpooler::PoolManager::GenericConnectionPool.new(
+ metrics: metrics,
+ metric_prefix: 'redis_connection_pool',
+ size: 1,
+ timeout: 5
+ ) { MockRedis.new }
+ }
+ let(:redis) { MockRedis.new }
+
+ let(:provider) { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, redis_connection_pool, 'mock_provider', provider_options) }
let(:config) { YAML.load(<<-EOT
---
:config: {}
:providers:
:mock:
+:redis: {}
:pools:
- name: '#{pool}'
size: 1
@@ -34,7 +44,7 @@ EOT
)
}
- subject { Vmpooler::PoolManager.new(config, logger, redis, metrics) }
+ subject { Vmpooler::PoolManager.new(config, logger, redis_connection_pool, metrics) }
describe '#config' do
before do
@@ -92,14 +102,18 @@ EOT
it 'calls move_pending_vm_to_ready if host is ready' do
expect(provider).to receive(:vm_ready?).with(pool,vm).and_return(true)
- expect(subject).to receive(:move_pending_vm_to_ready).with(vm, pool)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:move_pending_vm_to_ready).with(vm, pool, redis, nil)
+ end
subject._check_pending_vm(vm, pool, timeout, provider)
end
it 'calls fail_pending_vm if host is not ready' do
expect(provider).to receive(:vm_ready?).with(pool,vm).and_return(false)
- expect(subject).to receive(:fail_pending_vm).with(vm, pool, timeout)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:fail_pending_vm).with(vm, pool, timeout, redis)
+ end
subject._check_pending_vm(vm, pool, timeout, provider)
end
@@ -125,17 +139,20 @@ EOT
end
it 'removes VM from pending in redis' do
- create_pending_vm(pool,vm)
+ redis_connection_pool.with do |redis|
+ create_pending_vm(pool,vm,redis)
- expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(true)
- subject.remove_nonexistent_vm(vm, pool)
- expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(false)
+ subject.remove_nonexistent_vm(vm, pool, redis)
+ expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(false)
+ end
end
it 'logs msg' do
expect(logger).to receive(:log).with('d', "[!] [#{pool}] '#{vm}' no longer exists. Removing from pending.")
- subject.remove_nonexistent_vm(vm, pool)
+ redis_connection_pool.with do |redis|
+ subject.remove_nonexistent_vm(vm, pool, redis)
+ end
end
end
@@ -145,49 +162,78 @@ EOT
end
before(:each) do
- create_pending_vm(pool,vm)
+ redis_connection_pool.with do |redis|
+ create_pending_vm(pool,vm,redis)
+ config[:config]['vm_checktime'] = 15
+ end
end
it 'takes no action if VM is not cloning' do
- expect(subject.fail_pending_vm(vm, pool, timeout)).to eq(true)
- expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ expect(subject.fail_pending_vm(vm, pool, timeout, redis)).to eq(true)
+ end
end
it 'takes no action if VM is within timeout' do
- redis.hset("vmpooler__vm__#{vm}", 'clone',Time.now.to_s)
- expect(subject.fail_pending_vm(vm, pool, timeout)).to eq(true)
- expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone',Time.now.to_s)
+ expect(subject.fail_pending_vm(vm, pool, timeout, redis)).to eq(true)
+ expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(true)
+ end
end
it 'moves VM to completed queue if VM has exceeded timeout and exists' do
- redis.hset("vmpooler__vm__#{vm}", 'clone',Date.new(2001,1,1).to_s)
- expect(subject.fail_pending_vm(vm, pool, timeout,true)).to eq(true)
- expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(false)
- expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone',Date.new(2001,1,1).to_s)
+ expect(subject.fail_pending_vm(vm, pool, timeout, redis, true)).to eq(true)
+ expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(false)
+ expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
+ end
end
it 'logs message if VM has exceeded timeout and exists' do
- redis.hset("vmpooler__vm__#{vm}", 'clone',Date.new(2001,1,1).to_s)
- expect(logger).to receive(:log).with('d', "[!] [#{pool}] '#{vm}' marked as 'failed' after #{timeout} minutes")
- expect(subject.fail_pending_vm(vm, pool, timeout,true)).to eq(true)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone',Date.new(2001,1,1).to_s)
+ expect(logger).to receive(:log).with('d', "[!] [#{pool}] '#{vm}' marked as 'failed' after #{timeout} minutes")
+ expect(subject.fail_pending_vm(vm, pool, timeout, redis, true)).to eq(true)
+ end
end
it 'calls remove_nonexistent_vm if VM has exceeded timeout and does not exist' do
- redis.hset("vmpooler__vm__#{vm}", 'clone',Date.new(2001,1,1).to_s)
- expect(subject).to receive(:remove_nonexistent_vm).with(vm, pool)
- expect(subject.fail_pending_vm(vm, pool, timeout,false)).to eq(true)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone',Date.new(2001,1,1).to_s)
+ expect(subject).to receive(:remove_nonexistent_vm).with(vm, pool, redis)
+ expect(subject.fail_pending_vm(vm, pool, timeout, redis, false)).to eq(true)
+ end
end
it 'swallows error if an error is raised' do
- redis.hset("vmpooler__vm__#{vm}", 'clone','iamnotparsable_asdate')
- expect(subject.fail_pending_vm(vm, pool, timeout,true)).to eq(false)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone','iamnotparsable_asdate')
+ expect(subject.fail_pending_vm(vm, pool, timeout, redis, true)).to eq(false)
+ end
end
it 'logs message if an error is raised' do
- redis.hset("vmpooler__vm__#{vm}", 'clone','iamnotparsable_asdate')
- expect(logger).to receive(:log).with('d', String)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone','iamnotparsable_asdate')
+ expect(logger).to receive(:log).with('d', String)
- subject.fail_pending_vm(vm, pool, timeout,true)
+ subject.fail_pending_vm(vm, pool, timeout, redis, true)
+ end
+ end
+
+ context 'with request_id' do
+
+ it 'creates a new odcreate task' do
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone',(Time.now - 900).to_s)
+ redis.hset("vmpooler__vm__#{vm}", 'pool_alias', pool)
+ redis.hset("vmpooler__vm__#{vm}", 'request_id', request_id)
+ subject.fail_pending_vm(vm, pool, timeout, redis, true)
+ expect(redis.zrange('vmpooler__odcreate__task', 0, -1)).to eq(["#{pool}:#{pool}:1:#{request_id}"])
+ end
+ end
end
end
@@ -199,61 +245,167 @@ EOT
end
before(:each) do
- create_pending_vm(pool,vm)
+ redis_connection_pool.with do |redis|
+ create_pending_vm(pool,vm,redis)
+ end
end
context 'when hostname matches VM name' do
it 'should move the VM from pending to ready pool' do
- expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(true)
- expect(redis.sismember("vmpooler__ready__#{pool}", vm)).to be(false)
- subject.move_pending_vm_to_ready(vm, pool)
- expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(false)
- expect(redis.sismember("vmpooler__ready__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(true)
+ expect(redis.sismember("vmpooler__ready__#{pool}", vm)).to be(false)
+ subject.move_pending_vm_to_ready(vm, pool, redis)
+ expect(redis.sismember("vmpooler__pending__#{pool}", vm)).to be(false)
+ expect(redis.sismember("vmpooler__ready__#{pool}", vm)).to be(true)
+ end
end
it 'should log a message' do
expect(logger).to receive(:log).with('s', "[>] [#{pool}] '#{vm}' moved from 'pending' to 'ready' queue")
- subject.move_pending_vm_to_ready(vm, pool)
+ redis_connection_pool.with do |redis|
+ subject.move_pending_vm_to_ready(vm, pool, redis)
+ end
end
it 'should receive time_to_ready_state metric' do
- redis.hset("vmpooler__vm__#{vm}", 'clone',Time.now.to_s)
- expect(metrics).to receive(:timing).with(/time_to_ready_state\./,/0/)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone',Time.now.to_s)
+ expect(metrics).to receive(:timing).with(/time_to_ready_state\./,/0/)
- subject.move_pending_vm_to_ready(vm, pool)
+ subject.move_pending_vm_to_ready(vm, pool, redis)
+ end
end
it 'should set the boot time in redis' do
- redis.hset("vmpooler__vm__#{vm}", 'clone',Time.now.to_s)
- expect(redis.hget('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm)).to be_nil
- subject.move_pending_vm_to_ready(vm, pool)
- expect(redis.hget('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm)).to_not be_nil
- # TODO Should we inspect the value to see if it's valid?
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone',Time.now.to_s)
+ expect(redis.hget('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm)).to be_nil
+ subject.move_pending_vm_to_ready(vm, pool, redis)
+ expect(redis.hget('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm)).to_not be_nil
+ # TODO Should we inspect the value to see if it's valid?
+ end
end
it 'should not determine boot timespan if clone start time not set' do
- expect(redis.hget('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm)).to be_nil
- subject.move_pending_vm_to_ready(vm, pool)
- expect(redis.hget('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm)).to eq("") # Possible implementation bug here. Should still be nil here
+ redis_connection_pool.with do |redis|
+ expect(redis.hget('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm)).to be_nil
+ subject.move_pending_vm_to_ready(vm, pool, redis)
+ expect(redis.hget('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm)).to match(/\d\.\d{2}/)
+ end
end
it 'should raise error if clone start time is not parsable' do
- redis.hset("vmpooler__vm__#{vm}", 'clone','iamnotparsable_asdate')
- expect{subject.move_pending_vm_to_ready(vm, pool)}.to raise_error(/iamnotparsable_asdate/)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'clone','iamnotparsable_asdate')
+ expect{subject.move_pending_vm_to_ready(vm, pool, redis)}.to raise_error(/iamnotparsable_asdate/)
+ end
end
it 'should save the last boot time' do
- expect(redis.hget('vmpooler__lastboot', pool)).to be(nil)
- subject.move_pending_vm_to_ready(vm, pool)
- expect(redis.hget('vmpooler__lastboot', pool)).to_not be(nil)
+ redis_connection_pool.with do |redis|
+ expect(redis.hget('vmpooler__lastboot', pool)).to be(nil)
+ subject.move_pending_vm_to_ready(vm, pool, redis)
+ expect(redis.hget('vmpooler__lastboot', pool)).to_not be(nil)
+ end
+ end
+ end
+
+ context 'with request_id' do
+ context 'with a pending request' do
+ it 'sets the vm as active' do
+ redis_connection_pool.with do |redis|
+ expect(Time).to receive(:now).and_return(current_time).at_least(:once)
+ redis.hset("vmpooler__vm__#{vm}", 'pool_alias', pool)
+ subject.move_pending_vm_to_ready(vm, pool, redis, request_id)
+ expect(redis.hget("vmpooler__active__#{pool}", vm)).to eq(current_time.to_s)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to eq(current_time.to_s)
+ expect(redis.sismember("vmpooler__#{request_id}__#{pool}__#{pool}", vm)).to be true
+ end
+ end
+
+ it 'logs that the vm is ready for the request' do
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'pool_alias', pool)
+ expect(logger).to receive(:log).with('s', "[>] [#{pool}] '#{vm}' is 'ready' for request '#{request_id}'")
+
+ subject.move_pending_vm_to_ready(vm, pool, redis, request_id)
+ end
+ end
+ end
+
+ context 'when the request has been marked as failed' do
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__odrequest__#{request_id}", 'status', 'failed')
+ end
+ end
+
+ it 'moves the vm to completed' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:move_vm_queue).with(pool, vm, 'pending', 'completed', redis, "moved to completed queue. '#{request_id}' could not be filled in time")
+ subject.move_pending_vm_to_ready(vm, pool, redis, request_id)
+ end
+ end
+
+ it 'returns nil' do
+ redis_connection_pool.with do |redis|
+ result = subject.move_pending_vm_to_ready(vm, pool, redis, request_id)
+ expect(result).to be nil
+ end
+ end
+ end
+
+ context 'when the request has been marked as deleted' do
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__odrequest__#{request_id}", 'status', 'deleted')
+ end
+ end
+
+ it 'moves the vm to completed' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:move_vm_queue).with(pool, vm, 'pending', 'completed', redis, "moved to completed queue. '#{request_id}' has been deleted")
+ subject.move_pending_vm_to_ready(vm, pool, redis, request_id)
+ end
+ end
+
+ it 'returns nil' do
+ redis_connection_pool.with do |redis|
+ result = subject.move_pending_vm_to_ready(vm, pool, redis, request_id)
+ expect(result).to be nil
+ end
+ end
+ end
+
+ context 'with auth on the request' do
+ let(:user) { 'vmpuser' }
+ let(:platform_alias) { pool }
+ let(:platforms_string) { "#{platform_alias}:#{pool}:1" }
+ let(:score) { current_time.to_i }
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ create_ondemand_request_for_test(request_id, score, platforms_string, redis, user, token)
+ end
+ end
+
+ it 'should specify auth data on the vm' do
+ redis_connection_pool.with do |redis|
+ allow(redis).to receive(:hset)
+ expect(redis).to receive(:hset).with("vmpooler__vm__#{vm}", 'token:token', token)
+ expect(redis).to receive(:hset).with("vmpooler__vm__#{vm}", 'token:user', user)
+
+ subject.move_pending_vm_to_ready(vm, pool, redis, request_id)
+ end
+ end
end
end
end
describe '#check_ready_vm' do
- let(:ttl) { 0 }
+ let(:ttl) { 5 }
let(:poolconfig) { config[:pools][0] }
before do
@@ -269,7 +421,7 @@ EOT
end
describe '#_check_ready_vm' do
- let(:ttl) { 0 }
+ let(:ttl) { 5 }
let(:host) { {} }
let(:config) { YAML.load(<<-EOT
---
@@ -286,7 +438,9 @@ EOT
}
before(:each) do
- create_ready_vm(pool,vm)
+ redis_connection_pool.with do |redis|
+ create_ready_vm(pool,vm,redis)
+ end
config[:config]['vm_checktime'] = 15
# Create a VM which is powered on
@@ -297,41 +451,43 @@ EOT
context 'a VM that does not need to be checked' do
it 'should do nothing' do
- check_stamp = (Time.now - 60).to_s
- redis.hset("vmpooler__vm__#{vm}", 'check', check_stamp)
- expect(provider).to receive(:get_vm).exactly(0).times
- subject._check_ready_vm(vm, pool, ttl, provider)
- expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to eq(check_stamp)
+ redis_connection_pool.with do |redis|
+ check_stamp = (Time.now - 60).to_s
+ redis.hset("vmpooler__vm__#{vm}", 'check', check_stamp)
+ expect(provider).to receive(:get_vm).exactly(0).times
+ subject._check_ready_vm(vm, pool, ttl, provider)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to eq(check_stamp)
+ end
end
end
context 'a VM that has never been checked' do
- let(:last_check_date) { Date.new(2001,1,1).to_s }
-
it 'should set the current check timestamp' do
- expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to be_nil
- subject._check_ready_vm(vm, pool, ttl, provider)
- expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to_not be_nil
+ redis_connection_pool.with do |redis|
+ expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to be_nil
+ subject._check_ready_vm(vm, pool, ttl, provider)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to_not be_nil
+ end
end
end
context 'a VM that needs to be checked' do
- let(:last_check_date) { Date.new(2001,1,1).to_s }
+ let(:last_check_date) { Time.now - 901 }
before(:each) do
- redis.hset("vmpooler__vm__#{vm}", 'check',last_check_date)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'check',last_check_date)
+ end
end
it 'should set the current check timestamp' do
- expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to eq(last_check_date)
- subject._check_ready_vm(vm, pool, ttl, provider)
- expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to_not eq(last_check_date)
+ redis_connection_pool.with do |redis|
+ expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to eq(last_check_date.to_s)
+ subject._check_ready_vm(vm, pool, ttl, provider)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'check')).to_not eq(last_check_date.to_s)
+ end
end
context 'and is ready' do
- before(:each) do
- expect(provider).to receive(:vm_ready?).with(pool, vm).and_return(true)
- end
-
it 'should only set the next check interval' do
subject._check_ready_vm(vm, pool, ttl, provider)
end
@@ -339,21 +495,25 @@ EOT
context 'has correct name and is not ready' do
before(:each) do
- expect(provider).to receive(:vm_ready?).with(pool, vm).and_return(false)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'ready', Time.now - 200)
+ redis.sadd("vmpooler__ready__#{pool}", vm)
+ end
end
it 'should move the VM to the completed queue' do
- expect(redis).to receive(:smove).with("vmpooler__ready__#{pool}", "vmpooler__completed__#{pool}", vm)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:smove).with("vmpooler__ready__#{pool}", "vmpooler__completed__#{pool}", vm)
- subject._check_ready_vm(vm, pool, ttl, provider)
+ subject._check_ready_vm(vm, pool, ttl, provider)
+ end
end
it 'should move the VM to the completed queue in Redis' do
- expect(redis.sismember("vmpooler__ready__#{pool}", vm)).to be(true)
- expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(false)
- subject._check_ready_vm(vm, pool, ttl, provider)
- expect(redis.sismember("vmpooler__ready__#{pool}", vm)).to be(false)
- expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ subject._check_ready_vm(vm, pool, ttl, provider)
+ expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
+ end
end
it 'should log messages about being unreachable' do
@@ -367,7 +527,9 @@ EOT
context 'when less than 60 seconds since a VM moved to ready' do
before(:each) do
- redis.hset("vmpooler__vm__#{vm}", 'ready', Time.now)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'ready', Time.now)
+ end
end
it 'should return nil' do
@@ -377,29 +539,19 @@ EOT
context 'with a hostname mismatch' do
let(:different_hostname) { 'different_name' }
+ let(:longer_ttl) { 20 }
before(:each) do
- expect(provider).to receive(:get_vm).with(pool,vm).and_return(host)
host['hostname'] = different_hostname
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:mismatched_hostname?).with(vm, pool, provider, redis).and_return(true)
+ redis.hset("vmpooler__vm__#{vm}", 'ready', Time.now - 300)
+ end
end
- it 'should move the VM to the completed queue' do
- expect(redis).to receive(:smove).with("vmpooler__ready__#{pool}", "vmpooler__completed__#{pool}", vm)
+ it 'should not run vm_still_ready?' do
+ expect(subject).to_not receive(:vm_still_ready?)
- subject._check_ready_vm(vm, pool, ttl, provider)
- end
-
- it 'should move the VM to the completed queue in Redis' do
- expect(redis.sismember("vmpooler__ready__#{pool}", vm)).to be(true)
- expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(false)
- subject._check_ready_vm(vm, pool, ttl, provider)
- expect(redis.sismember("vmpooler__ready__#{pool}", vm)).to be(false)
- expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
- end
-
- it 'should log messages about being misnamed' do
- expect(logger).to receive(:log).with('d', "[!] [#{pool}] '#{vm}' has mismatched hostname #{different_hostname}, removed from 'ready' queue")
-
- subject._check_ready_vm(vm, pool, ttl, provider)
+ subject._check_ready_vm(vm, pool, longer_ttl, provider)
end
end
end
@@ -467,7 +619,9 @@ EOT
end
before(:each) do
- create_running_vm(pool,vm)
+ redis_connection_pool.with do |redis|
+ create_running_vm(pool,vm, redis)
+ end
# Create a VM which is powered on
host['hostname'] = vm
@@ -476,36 +630,44 @@ EOT
end
it 'moves a missing VM to the completed queue' do
- expect(provider).to receive(:vm_ready?).and_return(false)
- expect(provider).to receive(:get_vm).with(pool,vm).and_return(nil)
- expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
- subject._check_running_vm(vm, pool, timeout, provider)
- expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(false)
+ redis_connection_pool.with do |redis|
+ expect(provider).to receive(:vm_ready?).and_return(false)
+ expect(provider).to receive(:get_vm).with(pool,vm).and_return(nil)
+ expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
+ subject._check_running_vm(vm, pool, timeout, provider)
+ expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(false)
+ end
end
context 'valid host' do
it 'should not move VM if it has no checkout time' do
- expect(provider).to receive(:vm_ready?).and_return(true)
- expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
- subject._check_running_vm(vm, pool, 0, provider)
- expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ expect(provider).to receive(:vm_ready?).and_return(true)
+ expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
+ subject._check_running_vm(vm, pool, 0, provider)
+ expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
+ end
end
it 'should not move VM if TTL is zero' do
- expect(provider).to receive(:vm_ready?).and_return(true)
- redis.hset("vmpooler__active__#{pool}", vm,(Time.now - timeout*60*60).to_s)
- expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
- subject._check_running_vm(vm, pool, 0, provider)
- expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ expect(provider).to receive(:vm_ready?).and_return(true)
+ redis.hset("vmpooler__active__#{pool}", vm,(Time.now - timeout*60*60).to_s)
+ expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
+ subject._check_running_vm(vm, pool, 0, provider)
+ expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
+ end
end
it 'should move VM when past TTL' do
- redis.hset("vmpooler__active__#{pool}", vm,(Time.now - timeout*60*60).to_s)
- expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
- expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(false)
- subject._check_running_vm(vm, pool, timeout, provider)
- expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(false)
- expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__active__#{pool}", vm,(Time.now - timeout*60*60).to_s)
+ expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(true)
+ expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(false)
+ subject._check_running_vm(vm, pool, timeout, provider)
+ expect(redis.sismember("vmpooler__running__#{pool}", vm)).to be(false)
+ expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
+ end
end
end
@@ -533,31 +695,43 @@ EOT
end
before(:each) do
- create_pending_vm(pool, vm, token)
+ redis_connection_pool.with do |redis|
+ create_pending_vm(pool, vm, redis, token)
+ end
end
it 'VM should be in the "from queue" before the move' do
- expect(redis.sismember("vmpooler__#{queue_from}__#{pool}",vm))
+ redis_connection_pool.with do |redis|
+ expect(redis.sismember("vmpooler__#{queue_from}__#{pool}",vm))
+ end
end
it 'VM should not be in the "from queue" after the move' do
- subject.move_vm_queue(pool, vm, queue_from, queue_to, message)
- expect(!redis.sismember("vmpooler__#{queue_from}__#{pool}",vm))
+ redis_connection_pool.with do |redis|
+ subject.move_vm_queue(pool, vm, queue_from, queue_to, redis, message)
+ expect(!redis.sismember("vmpooler__#{queue_from}__#{pool}",vm))
+ end
end
it 'VM should not be in the "to queue" before the move' do
- expect(!redis.sismember("vmpooler__#{queue_to}__#{pool}",vm))
+ redis_connection_pool.with do |redis|
+ expect(!redis.sismember("vmpooler__#{queue_to}__#{pool}",vm))
+ end
end
it 'VM should be in the "to queue" after the move' do
- subject.move_vm_queue(pool, vm, queue_from, queue_to, message)
- expect(redis.sismember("vmpooler__#{queue_to}__#{pool}",vm))
+ redis_connection_pool.with do |redis|
+ subject.move_vm_queue(pool, vm, queue_from, queue_to, redis, message)
+ expect(redis.sismember("vmpooler__#{queue_to}__#{pool}",vm))
+ end
end
it 'should log a message' do
allow(logger).to receive(:log).with('d', "[!] [#{pool}] '#{vm}' #{message}")
- subject.move_vm_queue(pool, vm, queue_from, queue_to, message)
+ redis_connection_pool.with do |redis|
+ subject.move_vm_queue(pool, vm, queue_from, queue_to, redis, message)
+ end
end
end
@@ -570,7 +744,7 @@ EOT
end
it 'calls _clone_vm' do
- expect(subject).to receive(:_clone_vm).with(pool_object,provider)
+ expect(subject).to receive(:_clone_vm).with(pool_object,provider,nil,nil)
subject.clone_vm(pool_object,provider)
end
@@ -578,7 +752,7 @@ EOT
it 'logs a message if an error is raised' do
allow(logger).to receive(:log)
expect(logger).to receive(:log).with('s',"[!] [#{pool}] failed while cloning VM with an error: MockError")
- expect(subject).to receive(:_clone_vm).with(pool,provider).and_raise('MockError')
+ expect(subject).to receive(:_clone_vm).with(pool,provider,nil,nil).and_raise('MockError')
expect{subject.clone_vm(pool,provider)}.to raise_error(/MockError/)
end
@@ -605,32 +779,37 @@ EOT
context 'with no errors during cloning' do
before(:each) do
+ allow(metrics).to receive(:timing)
expect(metrics).to receive(:timing).with(/clone\./,/0/)
expect(provider).to receive(:create_vm).with(pool, String)
allow(logger).to receive(:log)
- expect(subject).to receive(:find_unique_hostname).with(pool).and_return(vm)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:find_unique_hostname).with(pool).and_return(vm)
+ end
end
it 'should create a cloning VM' do
- expect(redis.scard("vmpooler__pending__#{pool}")).to eq(0)
+ redis_connection_pool.with do |redis|
+ expect(redis.scard("vmpooler__pending__#{pool}")).to eq(0)
- subject._clone_vm(pool,provider)
+ subject._clone_vm(pool,provider)
- expect(redis.scard("vmpooler__pending__#{pool}")).to eq(1)
- # Get the new VM Name from the pending pool queue as it should be the only entry
- vm_name = redis.smembers("vmpooler__pending__#{pool}")[0]
- expect(redis.hget("vmpooler__vm__#{vm_name}", 'clone')).to_not be_nil
- expect(redis.hget("vmpooler__vm__#{vm_name}", 'template')).to eq(pool)
- expect(redis.hget("vmpooler__clone__#{Date.today.to_s}", "#{pool}:#{vm_name}")).to_not be_nil
- expect(redis.hget("vmpooler__vm__#{vm_name}", 'clone_time')).to_not be_nil
+ expect(redis.scard("vmpooler__pending__#{pool}")).to eq(1)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'clone')).to_not be_nil
+ expect(redis.hget("vmpooler__vm__#{vm}", 'template')).to eq(pool)
+ expect(redis.hget("vmpooler__clone__#{Date.today.to_s}", "#{pool}:#{vm}")).to_not be_nil
+ expect(redis.hget("vmpooler__vm__#{vm}", 'clone_time')).to_not be_nil
+ end
end
it 'should decrement the clone tasks counter' do
- redis.incr('vmpooler__tasks__clone')
- redis.incr('vmpooler__tasks__clone')
- expect(redis.get('vmpooler__tasks__clone')).to eq('2')
- subject._clone_vm(pool,provider)
- expect(redis.get('vmpooler__tasks__clone')).to eq('1')
+ redis_connection_pool.with do |redis|
+ redis.incr('vmpooler__tasks__clone')
+ redis.incr('vmpooler__tasks__clone')
+ expect(redis.get('vmpooler__tasks__clone')).to eq('2')
+ subject._clone_vm(pool,provider)
+ expect(redis.get('vmpooler__tasks__clone')).to eq('1')
+ end
end
it 'should log a message that is being cloned from a template' do
@@ -650,36 +829,72 @@ EOT
before(:each) do
expect(provider).to receive(:create_vm).with(pool, String).and_raise('MockError')
allow(logger).to receive(:log)
- expect(subject).to receive(:find_unique_hostname).with(pool).and_return(vm)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:find_unique_hostname).with(pool).and_return(vm)
+ end
end
it 'should not create a cloning VM' do
- expect(redis.scard("vmpooler__pending__#{pool}")).to eq(0)
+ redis_connection_pool.with do |redis|
+ expect(redis.scard("vmpooler__pending__#{pool}")).to eq(0)
- expect{subject._clone_vm(pool,provider)}.to raise_error(/MockError/)
+ expect{subject._clone_vm(pool,provider)}.to raise_error(/MockError/)
- expect(redis.scard("vmpooler__pending__#{pool}")).to eq(0)
- # Get the new VM Name from the pending pool queue as it should be the only entry
- vm_name = redis.smembers("vmpooler__pending__#{pool}")[0]
- expect(vm_name).to be_nil
+ expect(redis.scard("vmpooler__pending__#{pool}")).to eq(0)
+ # Get the new VM Name from the pending pool queue as it should be the only entry
+ vm_name = redis.smembers("vmpooler__pending__#{pool}")[0]
+ expect(vm_name).to be_nil
+ end
end
it 'should decrement the clone tasks counter' do
- redis.incr('vmpooler__tasks__clone')
- redis.incr('vmpooler__tasks__clone')
- expect(redis.get('vmpooler__tasks__clone')).to eq('2')
- expect{subject._clone_vm(pool,provider)}.to raise_error(/MockError/)
- expect(redis.get('vmpooler__tasks__clone')).to eq('1')
+ redis_connection_pool.with do |redis|
+ redis.incr('vmpooler__tasks__clone')
+ redis.incr('vmpooler__tasks__clone')
+ expect(redis.get('vmpooler__tasks__clone')).to eq('2')
+ expect{subject._clone_vm(pool,provider)}.to raise_error(/MockError/)
+ expect(redis.get('vmpooler__tasks__clone')).to eq('1')
+ end
end
it 'should expire the vm metadata' do
- expect(redis).to receive(:expire)
- expect{subject._clone_vm(pool,provider)}.to raise_error(/MockError/)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:expire)
+ expect{subject._clone_vm(pool,provider)}.to raise_error(/MockError/)
+ end
end
it 'should raise the error' do
expect{subject._clone_vm(pool,provider)}.to raise_error(/MockError/)
end
+
+ end
+
+ context 'with request_id' do
+ before(:each) do
+ allow(metrics).to receive(:timing)
+ expect(metrics).to receive(:timing).with(/clone\./,/0/)
+ expect(provider).to receive(:create_vm).with(pool, String)
+ allow(logger).to receive(:log)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:find_unique_hostname).with(pool).and_return(vm)
+ end
+ end
+
+ it 'should set request_id and pool_alias on the vm data' do
+ redis_connection_pool.with do |redis|
+ subject._clone_vm(pool,provider,request_id,pool)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'pool_alias')).to eq(pool)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'request_id')).to eq(request_id)
+ end
+ end
+
+ it 'should reduce the clone count' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:decr).with('vmpooler__tasks__ondemandclone')
+ subject._clone_vm(pool,provider,request_id,pool)
+ end
+ end
end
end
@@ -708,7 +923,9 @@ EOT
before(:each) do
expect(subject).not_to be_nil
- create_completed_vm(vm,pool,true)
+ redis_connection_pool.with do |redis|
+ create_completed_vm(vm,pool,redis,true)
+ end
allow(provider).to receive(:destroy_vm).with(pool,vm).and_return(true)
@@ -723,9 +940,10 @@ EOT
end
it 'should call redis expire with 0' do
- expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to_not be_nil
- subject._destroy_vm(vm,pool,provider)
- expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to be_nil
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:expire).with("vmpooler__vm__#{vm}", 0)
+ subject._destroy_vm(vm,pool,provider)
+ end
end
end
@@ -760,13 +978,16 @@ EOT
it 'should emit a timing metric' do
allow(subject).to receive(:get_vm_usage_labels)
+ allow(metrics).to receive(:timing)
expect(metrics).to receive(:timing).with("destroy.#{pool}", String)
subject._destroy_vm(vm,pool,provider)
end
it 'should check usage labels' do
- expect(subject).to receive(:get_vm_usage_labels).with(vm)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:get_vm_usage_labels).with(vm, redis)
+ end
subject._destroy_vm(vm,pool,provider)
end
@@ -820,7 +1041,9 @@ EOT
context 'when label evaluation is disabled' do
it 'should do nothing' do
- subject.get_vm_usage_labels(vm)
+ redis_connection_pool.with do |redis|
+ subject.get_vm_usage_labels(vm, redis)
+ end
end
end
@@ -832,13 +1055,17 @@ EOT
context 'when a VM has not been checked out' do
before(:each) do
- create_ready_vm(template, vm)
+ redis_connection_pool.with do |redis|
+ create_ready_vm(template, vm, redis)
+ end
end
it 'should return' do
expect(subject).to receive(:get_vm_usage_labels).and_return(nil)
- subject.get_vm_usage_labels(vm)
+ redis_connection_pool.with do |redis|
+ subject.get_vm_usage_labels(vm, redis)
+ end
end
end
@@ -847,26 +1074,34 @@ EOT
context 'without auth' do
before(:each) do
- create_running_vm(template, vm)
+ redis_connection_pool.with do |redis|
+ create_running_vm(template, vm, redis)
+ end
end
it 'should emit a metric' do
- expect(metrics).to receive(:increment).with("usage.unauthenticated.#{template}")
+ redis_connection_pool.with do |redis|
+ expect(metrics).to receive(:increment).with("usage.unauthenticated.#{template}")
- subject.get_vm_usage_labels(vm)
+ subject.get_vm_usage_labels(vm, redis)
+ end
end
end
context 'with auth' do
before(:each) do
- create_running_vm(template, vm, token, user)
+ redis_connection_pool.with do |redis|
+ create_running_vm(template, vm, redis, token, user)
+ end
end
it 'should emit a metric' do
expect(metrics).to receive(:increment).with("usage.#{user}.#{template}")
- subject.get_vm_usage_labels(vm)
+ redis_connection_pool.with do |redis|
+ subject.get_vm_usage_labels(vm, redis)
+ end
end
context 'with a user with period in name' do
@@ -875,13 +1110,17 @@ EOT
let(:metric_nodes) { metric_string.split('.') }
before(:each) do
- create_running_vm(template, vm)
+ redis_connection_pool.with do |redis|
+ create_running_vm(template, vm, redis)
+ end
end
it 'should emit a metric with the character replaced' do
expect(metrics).to receive(:increment).with(metric_string)
- subject.get_vm_usage_labels(vm)
+ redis_connection_pool.with do |redis|
+ subject.get_vm_usage_labels(vm, redis)
+ end
end
it 'should include three nodes' do
@@ -911,13 +1150,17 @@ EOT
let(:metric_string) { metric_string_sub.join('.') }
before(:each) do
- create_tag(vm, 'jenkins_build_url', jenkins_build_url)
+ redis_connection_pool.with do |redis|
+ create_tag(vm, 'jenkins_build_url', jenkins_build_url, redis)
+ end
end
it 'should emit a metric with information from the URL' do
expect(metrics).to receive(:increment).with(metric_string)
- subject.get_vm_usage_labels(vm)
+ redis_connection_pool.with do |redis|
+ subject.get_vm_usage_labels(vm, redis)
+ end
end
end
@@ -936,13 +1179,17 @@ EOT
let(:metric_nodes) { expected_string.split('.') }
before(:each) do
- create_tag(vm, 'jenkins_build_url', jenkins_build_url)
+ redis_connection_pool.with do |redis|
+ create_tag(vm, 'jenkins_build_url', jenkins_build_url, redis)
+ end
end
it 'should emit a metric with information from the URL' do
expect(metrics).to receive(:increment).with(expected_string)
- subject.get_vm_usage_labels(vm)
+ redis_connection_pool.with do |redis|
+ subject.get_vm_usage_labels(vm, redis)
+ end
end
it 'should contain exactly nine nodes' do
@@ -961,13 +1208,17 @@ EOT
let(:job_name) { value_stream_parts.join('_') }
before(:each) do
- create_tag(vm, 'jenkins_build_url', jenkins_build_url)
+ redis_connection_pool.with do |redis|
+ create_tag(vm, 'jenkins_build_url', jenkins_build_url, redis)
+ end
end
it 'should emit a metric with information from the URL without a build_component' do
expect(metrics).to receive(:increment).with("usage.#{user}.#{instance}.#{value_stream}.#{branch}.#{project}.#{job_name}.#{template}")
- subject.get_vm_usage_labels(vm)
+ redis_connection_pool.with do |redis|
+ subject.get_vm_usage_labels(vm, redis)
+ end
end
end
end
@@ -1096,15 +1347,15 @@ EOT
}
it 'should return a list of pool folders' do
- expect($providers[provider_name]).to receive(:get_target_datacenter_from_config).with(pool).and_return(datacenter)
+ expect(provider).to receive(:get_target_datacenter_from_config).with(pool).and_return(datacenter)
- expect(subject.pool_folders(provider_name)).to eq(expected_response)
+ expect(subject.pool_folders(provider)).to eq(expected_response)
end
it 'should raise an error when the provider fails to get the datacenter' do
- expect($providers[provider_name]).to receive(:get_target_datacenter_from_config).with(pool).and_raise('mockerror')
+ expect(provider).to receive(:get_target_datacenter_from_config).with(pool).and_raise('mockerror')
- expect{ subject.pool_folders(provider_name) }.to raise_error(RuntimeError, 'mockerror')
+ expect{ subject.pool_folders(provider) }.to raise_error(RuntimeError, 'mockerror')
end
end
@@ -1135,16 +1386,16 @@ EOT
it 'should run purge_unconfigured_folders' do
expect(subject).to receive(:pool_folders).and_return(configured_folders)
- expect($providers[provider_name]).to receive(:purge_unconfigured_folders).with(base_folders, configured_folders, whitelist)
- expect($providers[provider_name]).to receive(:provider_config).and_return({})
+ expect(provider).to receive(:purge_unconfigured_folders).with(base_folders, configured_folders, whitelist)
+ expect(provider).to receive(:provider_config).and_return({})
subject.purge_vms_and_folders(provider)
end
it 'should raise any errors' do
expect(subject).to receive(:pool_folders).and_return(configured_folders)
- expect($providers[provider_name]).to receive(:purge_unconfigured_folders).with(base_folders, configured_folders, whitelist).and_raise('mockerror')
- expect($providers[provider_name]).to receive(:provider_config).and_return({})
+ expect(provider).to receive(:purge_unconfigured_folders).with(base_folders, configured_folders, whitelist).and_raise('mockerror')
+ expect(provider).to receive(:provider_config).and_return({})
expect{ subject.purge_vms_and_folders(provider) }.to raise_error(RuntimeError, 'mockerror')
end
@@ -1173,7 +1424,9 @@ EOT
expect(subject).not_to be_nil
allow(logger).to receive(:log)
- create_running_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ create_running_vm(pool,vm,redis,token)
+ end
end
context 'Given a VM that does not exist' do
@@ -1183,8 +1436,10 @@ EOT
end
it 'should not update redis if the VM does not exist' do
- expect(redis).to receive(:hset).exactly(0).times
- expect{ subject._create_vm_disk(pool, vm, disk_size, provider) }.to raise_error(RuntimeError)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hset).exactly(0).times
+ expect{ subject._create_vm_disk(pool, vm, disk_size, provider) }.to raise_error(RuntimeError)
+ end
end
end
@@ -1201,8 +1456,10 @@ EOT
end
it 'should raise an error if the disk size is a Fixnum' do
- expect(redis).to receive(:hset).exactly(0).times
- expect{ subject._create_vm_disk(pool, vm, 10, provider) }.to raise_error(NoMethodError,/empty?/)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hset).exactly(0).times
+ expect{ subject._create_vm_disk(pool, vm, 10, provider) }.to raise_error(NoMethodError,/empty?/)
+ end
end
end
@@ -1219,16 +1476,20 @@ EOT
end
it 'should update redis information when attaching the first disk' do
- subject._create_vm_disk(pool, vm, disk_size, provider)
- expect(redis.hget("vmpooler__vm__#{vm}", 'disk')).to eq("+#{disk_size}gb")
+ redis_connection_pool.with do |redis|
+ subject._create_vm_disk(pool, vm, disk_size, provider)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'disk')).to eq("+#{disk_size}gb")
+ end
end
it 'should update redis information when attaching the additional disks' do
- initial_disks = '+10gb:+20gb'
- redis.hset("vmpooler__vm__#{vm}", 'disk', initial_disks)
+ redis_connection_pool.with do |redis|
+ initial_disks = '+10gb:+20gb'
+ redis.hset("vmpooler__vm__#{vm}", 'disk', initial_disks)
- subject._create_vm_disk(pool, vm, disk_size, provider)
- expect(redis.hget("vmpooler__vm__#{vm}", 'disk')).to eq("#{initial_disks}:+#{disk_size}gb")
+ subject._create_vm_disk(pool, vm, disk_size, provider)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'disk')).to eq("#{initial_disks}:+#{disk_size}gb")
+ end
end
end
@@ -1238,10 +1499,12 @@ EOT
end
it 'should not update redis information' do
- expect(redis).to receive(:hset).exactly(0).times
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hset).exactly(0).times
- subject._create_vm_disk(pool, vm, disk_size, provider)
- expect(redis.hget("vmpooler__vm__#{vm}", 'disk')).to be_nil
+ subject._create_vm_disk(pool, vm, disk_size, provider)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'disk')).to be_nil
+ end
end
it 'should log a message' do
@@ -1275,7 +1538,9 @@ EOT
end
before(:each) do
- create_running_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ create_running_vm(pool,vm,redis,token)
+ end
end
context 'Given a Pool that does not exist' do
@@ -1286,9 +1551,11 @@ EOT
end
it 'should not update redis' do
- expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
- expect{ subject._create_vm_snapshot(missing_pool, vm, snapshot_name, provider) }.to raise_error("Pool #{missing_pool} not found")
- expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ redis_connection_pool.with do |redis|
+ expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ expect{ subject._create_vm_snapshot(missing_pool, vm, snapshot_name, provider) }.to raise_error("Pool #{missing_pool} not found")
+ expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ end
end
end
@@ -1299,9 +1566,11 @@ EOT
end
it 'should not update redis' do
- expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
- expect{ subject._create_vm_snapshot(pool, missing_vm, snapshot_name, provider) }.to raise_error("VM #{missing_vm} not found")
- expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ redis_connection_pool.with do |redis|
+ expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ expect{ subject._create_vm_snapshot(pool, missing_vm, snapshot_name, provider) }.to raise_error("VM #{missing_vm} not found")
+ expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ end
end
end
@@ -1318,9 +1587,11 @@ EOT
end
it 'should add snapshot redis information' do
- expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
- subject._create_vm_snapshot(pool, vm, snapshot_name, provider)
- expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to_not be_nil
+ redis_connection_pool.with do |redis|
+ expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ subject._create_vm_snapshot(pool, vm, snapshot_name, provider)
+ expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to_not be_nil
+ end
end
end
@@ -1337,9 +1608,11 @@ EOT
end
it 'should not update redis' do
- expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
- subject._create_vm_snapshot(pool, vm, snapshot_name, provider)
- expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ redis_connection_pool.with do |redis|
+ expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ subject._create_vm_snapshot(pool, vm, snapshot_name, provider)
+ expect(redis.hget("vmpooler__vm__#{vm}", "snapshot:#{snapshot_name}")).to be_nil
+ end
end
end
end
@@ -1433,17 +1706,23 @@ EOT
describe '#get_pool_name_for_vm' do
context 'Given a valid VM' do
before(:each) do
- create_running_vm(pool, vm, token)
+ redis_connection_pool.with do |redis|
+ create_running_vm(pool, vm, redis, token)
+ end
end
it 'should return the pool name' do
- expect(subject.get_pool_name_for_vm(vm)).to eq(pool)
+ redis_connection_pool.with do |redis|
+ expect(subject.get_pool_name_for_vm(vm,redis)).to eq(pool)
+ end
end
end
context 'Given an invalid VM' do
it 'should return nil' do
- expect(subject.get_pool_name_for_vm('does_not_exist')).to be_nil
+ redis_connection_pool.with do |redis|
+ expect(subject.get_pool_name_for_vm('does_not_exist',redis)).to be_nil
+ end
end
end
end
@@ -1612,7 +1891,9 @@ EOT
context 'when VM in the queue does not exist' do
before(:each) do
- disk_task_vm(vm,"snapshot_#{vm}")
+ redis_connection_pool.with do |redis|
+ disk_task_vm(vm,"snapshot_#{vm}",redis)
+ end
end
it 'should log an error' do
@@ -1630,9 +1911,11 @@ EOT
context 'when specified provider does not exist' do
before(:each) do
- disk_task_vm(vm,"snapshot_#{vm}")
- create_running_vm(pool, vm, token)
- expect(subject).to receive(:get_provider_for_pool).and_return(nil)
+ redis_connection_pool.with do |redis|
+ disk_task_vm(vm,"snapshot_#{vm}",redis)
+ create_running_vm(pool, vm, redis, token)
+ expect(subject).to receive(:get_provider_for_pool).and_return(nil)
+ end
end
it 'should log an error' do
@@ -1651,8 +1934,10 @@ EOT
context 'when multiple VMs in the queue' do
before(:each) do
['vm1', 'vm2', 'vm3'].each do |vm_name|
- disk_task_vm(vm_name,"snapshot_#{vm_name}")
- create_running_vm(pool, vm_name, token)
+ redis_connection_pool.with do |redis|
+ disk_task_vm(vm_name,"snapshot_#{vm_name}",redis)
+ create_running_vm(pool, vm_name, redis, token)
+ end
end
allow(subject).to receive(:get_provider_for_pool).with(pool).and_return(provider)
@@ -1754,7 +2039,9 @@ EOT
context 'when VM in the queue does not exist' do
before(:each) do
- snapshot_vm(vm,"snapshot_#{vm}")
+ redis_connection_pool.with do |redis|
+ snapshot_vm(vm,"snapshot_#{vm}",redis)
+ end
end
it 'should log an error' do
@@ -1772,9 +2059,11 @@ EOT
context 'when specified provider does not exist' do
before(:each) do
- snapshot_vm(vm,"snapshot_#{vm}")
- create_running_vm(pool, vm, token)
- expect(subject).to receive(:get_provider_for_pool).and_return(nil)
+ redis_connection_pool.with do |redis|
+ snapshot_vm(vm,"snapshot_#{vm}",redis)
+ create_running_vm(pool, vm, redis, token)
+ expect(subject).to receive(:get_provider_for_pool).and_return(nil)
+ end
end
it 'should log an error' do
@@ -1793,8 +2082,10 @@ EOT
context 'when multiple VMs in the queue' do
before(:each) do
['vm1', 'vm2', 'vm3'].each do |vm_name|
- snapshot_vm(vm_name,"snapshot_#{vm_name}")
- create_running_vm(pool, vm_name, token)
+ redis_connection_pool.with do |redis|
+ snapshot_vm(vm_name,"snapshot_#{vm_name}",redis)
+ create_running_vm(pool, vm_name, redis, token)
+ end
end
allow(subject).to receive(:get_provider_for_pool).with(pool).and_return(provider)
@@ -1828,7 +2119,9 @@ EOT
context 'when VM in the queue does not exist' do
before(:each) do
- snapshot_revert_vm(vm,"snapshot_#{vm}")
+ redis_connection_pool.with do |redis|
+ snapshot_revert_vm(vm,"snapshot_#{vm}",redis)
+ end
end
it 'should log an error' do
@@ -1846,9 +2139,11 @@ EOT
context 'when specified provider does not exist' do
before(:each) do
- snapshot_revert_vm(vm,"snapshot_#{vm}")
- create_running_vm(pool, vm, token)
- expect(subject).to receive(:get_provider_for_pool).and_return(nil)
+ redis_connection_pool.with do |redis|
+ snapshot_revert_vm(vm,"snapshot_#{vm}",redis)
+ create_running_vm(pool, vm, redis, token)
+ expect(subject).to receive(:get_provider_for_pool).and_return(nil)
+ end
end
it 'should log an error' do
@@ -1867,8 +2162,10 @@ EOT
context 'when multiple VMs in the queue' do
before(:each) do
['vm1', 'vm2', 'vm3'].each do |vm_name|
- snapshot_revert_vm(vm_name,"snapshot_#{vm_name}")
- create_running_vm(pool, vm_name, token)
+ redis_connection_pool.with do |redis|
+ snapshot_revert_vm(vm_name,"snapshot_#{vm_name}",redis)
+ create_running_vm(pool, vm_name, redis, token)
+ end
end
allow(subject).to receive(:get_provider_for_pool).with(pool).and_return(provider)
@@ -1982,15 +2279,18 @@ EOT
end
it 'returns when a template is set and matches the configured template' do
- redis.hset('vmpooler__config__template', pool, old_template)
-
+ redis_connection_pool.with do |redis|
+ redis.hset('vmpooler__config__template', pool, old_template)
+ end
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)
+ redis_connection_pool.with do |redis|
+ redis.hset('vmpooler__config__template', pool, new_template)
+ end
subject.sync_pool_template(config[:pools][0])
@@ -2030,7 +2330,9 @@ EOT
end
it 'should set the pool template to match the configured template' do
- subject.update_pool_template(poolconfig, provider, new_template, current_template)
+ redis_connection_pool.with do |redis|
+ subject.update_pool_template(poolconfig, provider, new_template, current_template, redis)
+ end
expect(poolconfig['template']).to eq(new_template)
end
@@ -2038,31 +2340,41 @@ EOT
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)
+ redis_connection_pool.with do |redis|
+ subject.update_pool_template(poolconfig, provider, new_template, current_template, redis)
+ end
end
it 'should run drain_pool' do
- expect(subject).to receive(:drain_pool).with(pool)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:drain_pool).with(pool, redis)
- subject.update_pool_template(poolconfig, provider, new_template, current_template)
+ subject.update_pool_template(poolconfig, provider, new_template, current_template, redis)
+ end
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)
+ redis_connection_pool.with do |redis|
+ subject.update_pool_template(poolconfig, provider, new_template, current_template, redis)
+ end
end
it 'should run prepare_template' do
- expect(subject).to receive(:prepare_template).with(poolconfig, provider)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:prepare_template).with(poolconfig, provider, redis)
- subject.update_pool_template(poolconfig, provider, new_template, current_template)
+ subject.update_pool_template(poolconfig, provider, new_template, current_template, redis)
+ end
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)
+ redis_connection_pool.with do |redis|
+ subject.update_pool_template(poolconfig, provider, new_template, current_template, redis)
+ end
end
end
@@ -2092,8 +2404,11 @@ EOT
context 'when the mutex is locked' do
let(:mutex) { Mutex.new }
before(:each) do
- expect(redis).to receive(:scard).with("vmpooler__pending__#{pool}").and_return(1)
- expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(2)
+ redis_connection_pool.with do |redis|
+ create_ready_vm(pool,'vm1',redis)
+ create_ready_vm(pool,'vm2',redis)
+ create_pending_vm(pool,'vm3',redis)
+ end
mutex.lock
expect(subject).to receive(:pool_mutex).with(pool).and_return(mutex)
end
@@ -2105,28 +2420,35 @@ EOT
context 'with a total size less than the pool size' do
it 'should return nil' do
- expect(redis).to receive(:scard).with("vmpooler__pending__#{pool}").and_return(1)
- expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(1)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:scard).with("vmpooler__pending__#{pool}").and_return(1)
+ expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(1)
+ end
expect(subject.remove_excess_vms(config[:pools][0])).to be_nil
end
end
context 'with a total size greater than the pool size' do
it 'should remove excess ready vms' do
- expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(4)
- expect(redis).to receive(:scard).with("vmpooler__pending__#{pool}").and_return(0)
+ redis_connection_pool.with do |redis|
+ ['vm1','vm2','vm3','vm4'].each do |v|
+ create_ready_vm(pool,v,redis)
+ end
+ end
expect(subject).to receive(:move_vm_queue).exactly(2).times
subject.remove_excess_vms(config[:pools][0])
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
+ redis_connection_pool.with do |redis|
+ create_pending_vm(pool,'vm1',redis)
+ create_pending_vm(pool,'vm2',redis)
+ create_ready_vm(pool,'vm3',redis)
+ create_ready_vm(pool,'vm4',redis)
+ create_ready_vm(pool,'vm5',redis)
+ expect(subject).to receive(:move_vm_queue).exactly(3).times
+ end
subject.remove_excess_vms(config[:pools][0])
end
@@ -2150,33 +2472,43 @@ EOT
context 'when creating the template delta disks' do
before(:each) do
- allow(redis).to receive(:hset)
+ redis_connection_pool.with do |redis|
+ allow(redis).to receive(:hset)
+ end
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])
+ redis_connection_pool.with do |redis|
+ expect(provider).to receive(:create_template_delta_disks).with(config[:pools][0])
- subject.prepare_template(config[:pools][0], provider)
+ subject.prepare_template(config[:pools][0], provider, redis)
+ end
end
it 'should mark the template as prepared' do
- expect(redis).to receive(:hset).with('vmpooler__template__prepared', pool, config[:pools][0]['template'])
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hset).with('vmpooler__template__prepared', pool, config[:pools][0]['template'])
- subject.prepare_template(config[:pools][0], provider)
+ subject.prepare_template(config[:pools][0], provider, redis)
+ end
end
end
context 'when template delta disk creation fails' do
before(:each) do
- allow(redis).to receive(:hset)
+ redis_connection_pool.with do |redis|
+ allow(redis).to receive(:hset)
+ end
expect(provider).to receive(:create_template_delta_disks).and_raise("MockError")
end
it 'should log a message when delta disk creation returns an error' do
- expect(logger).to receive(:log).with('s', "[!] [#{pool}] failed while preparing a template with an error. As a result vmpooler could not create the template delta disks. Either a template delta disk already exists, or the template delta disk creation failed. The error is: MockError")
+ redis_connection_pool.with do |redis|
+ expect(logger).to receive(:log).with('s', "[!] [#{pool}] failed while preparing a template with an error. As a result vmpooler could not create the template delta disks. Either a template delta disk already exists, or the template delta disk creation failed. The error is: MockError")
- subject.prepare_template(config[:pools][0], provider)
+ subject.prepare_template(config[:pools][0], provider, redis)
+ end
end
end
end
@@ -2200,18 +2532,24 @@ EOT
}
before(:each) do
- allow(redis).to receive(:hget)
+ redis_connection_pool.with do |redis|
+ allow(redis).to receive(:hget)
+ end
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)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(current_template)
+ end
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)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__template', pool).and_return(new_template)
+ end
subject.evaluate_template(config[:pools][0], provider)
end
@@ -2229,14 +2567,18 @@ EOT
context 'when prepared template is nil' do
it 'should prepare the template' do
- expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(nil)
- expect(subject).to receive(:prepare_template).with(config[:pools][0], provider)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(nil)
+ expect(subject).to receive(:prepare_template).with(config[:pools][0], provider, redis)
+ end
subject.evaluate_template(config[:pools][0], provider)
end
it 'should not prepare the template again' do
- expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(current_template)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(current_template)
+ end
expect(subject).to_not receive(:prepare_template).with(config[:pools][0], provider)
subject.evaluate_template(config[:pools][0], provider)
@@ -2245,19 +2587,25 @@ EOT
context 'when the configured pool template does not match the prepared template' do
before(:each) do
- config[:pools][0]['template'] = new_template
- expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(current_template)
+ redis_connection_pool.with do |redis|
+ config[:pools][0]['template'] = new_template
+ expect(redis).to receive(:hget).with('vmpooler__template__prepared', pool).and_return(current_template)
+ end
end
it 'should prepare the template' do
- expect(subject).to receive(:prepare_template).with(config[:pools][0], provider)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:prepare_template).with(config[:pools][0], provider, redis)
+ end
subject.evaluate_template(config[:pools][0], provider)
end
context 'if configured_template is provided' do
it 'should not run prepare_template' do
- expect(redis).to receive(:hget).with('vmpooler__config__template', pool).and_return(current_template)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__template', pool).and_return(current_template)
+ end
expect(subject).to_not receive(:prepare_template)
subject.evaluate_template(config[:pools][0], provider)
@@ -2267,12 +2615,16 @@ EOT
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)
+ redis_connection_pool.with do |redis|
+ 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
end
it 'should update the template' do
- expect(subject).to receive(:update_pool_template).with(config[:pools][0], provider, new_template, current_template)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:update_pool_template).with(config[:pools][0], provider, new_template, current_template, redis)
+ end
subject.evaluate_template(config[:pools][0], provider)
end
@@ -2286,57 +2638,75 @@ EOT
context 'with no vms' do
it 'should return nil' do
- expect(subject.drain_pool(pool)).to be_nil
+ redis_connection_pool.with do |redis|
+ expect(subject.drain_pool(pool, redis)).to be_nil
+ end
end
it 'should not log any messages' do
expect(logger).to_not receive(:log)
- subject.drain_pool(pool)
+ redis_connection_pool.with do |redis|
+ subject.drain_pool(pool, redis)
+ end
end
it 'should not try to move any vms' do
expect(subject).to_not receive(:move_vm_queue)
- subject.drain_pool(pool)
+ redis_connection_pool.with do |redis|
+ subject.drain_pool(pool, redis)
+ end
end
end
context 'with ready vms' do
before(:each) do
- create_ready_vm(pool, 'vm1')
- create_ready_vm(pool, 'vm2')
+ redis_connection_pool.with do |redis|
+ create_ready_vm(pool, 'vm1', redis)
+ create_ready_vm(pool, 'vm2', redis)
+ end
end
it 'removes the ready instances' do
expect(subject).to receive(:move_vm_queue).twice
- subject.drain_pool(pool)
+ redis_connection_pool.with do |redis|
+ subject.drain_pool(pool, redis)
+ end
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)
+ redis_connection_pool.with do |redis|
+ subject.drain_pool(pool, redis)
+ end
end
end
context 'with pending instances' do
before(:each) do
- create_pending_vm(pool, 'vm1')
- create_pending_vm(pool, 'vm2')
+ redis_connection_pool.with do |redis|
+ create_pending_vm(pool, 'vm1', redis)
+ create_pending_vm(pool, 'vm2', redis)
+ end
end
it 'removes the pending instances' do
expect(subject).to receive(:move_vm_queue).twice
- subject.drain_pool(pool)
+ redis_connection_pool.with do |redis|
+ subject.drain_pool(pool, redis)
+ end
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)
+ redis_connection_pool.with do |redis|
+ subject.drain_pool(pool, redis)
+ end
end
end
end
@@ -2368,25 +2738,33 @@ EOT
end
it 'should get the pool size configuration from redis' do
- expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool)
+ end
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)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool).and_return(nil)
+ end
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')
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool).and_return('2')
+ end
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)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__poolsize', pool).and_return(newsize)
+ end
subject.update_pool_size(poolconfig)
@@ -2421,25 +2799,33 @@ EOT
end
it 'should get the pool clone target configuration from redis' do
- expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool)
+ end
subject.update_clone_target(poolconfig)
end
it 'should return when clone_target is not set in redis' do
- expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return(nil)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return(nil)
+ end
expect(subject.update_clone_target(poolconfig)).to be_nil
end
it 'should return when no change in configuration is required' do
- expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return('cluster1')
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return('cluster1')
+ end
expect(subject.update_clone_target(poolconfig)).to be_nil
end
it 'should update the clone target' do
- expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return(newtarget)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return(newtarget)
+ end
subject.update_clone_target(poolconfig)
@@ -2463,6 +2849,7 @@ EOT
allow(subject).to receive(:check_disk_queue)
allow(subject).to receive(:check_snapshot_queue)
allow(subject).to receive(:check_pool)
+ allow(subject).to receive(:check_ondemand_requests)
allow(logger).to receive(:log)
end
@@ -2480,15 +2867,19 @@ EOT
end
it 'should set clone tasks to zero' do
- redis.set('vmpooler__tasks__clone', 1)
- subject.execute!(1,0)
- expect(redis.get('vmpooler__tasks__clone')).to eq('0')
+ redis_connection_pool.with do |redis|
+ redis.set('vmpooler__tasks__clone', 1)
+ subject.execute!(1,0)
+ expect(redis.get('vmpooler__tasks__clone')).to eq('0')
+ end
end
it 'should clear migration tasks' do
- redis.set('vmpooler__migration', 1)
- subject.execute!(1,0)
- expect(redis.get('vmpooler__migration')).to be_nil
+ redis_connection_pool.with do |redis|
+ redis.set('vmpooler__migration', 1)
+ subject.execute!(1,0)
+ expect(redis.get('vmpooler__migration')).to be_nil
+ end
end
it 'should run the check_disk_queue method' do
@@ -2509,6 +2900,12 @@ EOT
subject.execute!(1,0)
end
+ it 'should run the check_ondemand_requests method' do
+ expect(subject).to receive(:check_ondemand_requests)
+
+ subject.execute!(1,0)
+ end
+
context 'creating Providers' do
let(:vsphere_provider) { double('vsphere_provider') }
let(:config) {
@@ -2526,7 +2923,7 @@ EOT
it 'should call create_provider_object idempotently' do
# Even though there are two pools using the vsphere provider (the default), it should only
# create the provider object once.
- expect(subject).to receive(:create_provider_object).with(Object, Object, Object, 'vsphere', 'vsphere', Object).and_return(vsphere_provider)
+ expect(subject).to receive(:create_provider_object).with(Object, Object, Object, redis_connection_pool, 'vsphere', 'vsphere', Object).and_return(vsphere_provider)
subject.execute!(1,0)
end
@@ -2549,8 +2946,8 @@ EOT
context 'creating multiple vsphere Providers' do
let(:vsphere_provider) { double('vsphere_provider') }
let(:vsphere_provider2) { double('vsphere_provider') }
- let(:provider1) { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, 'vsphere', provider_options) }
- let(:provider2) { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, 'secondvsphere', provider_options) }
+ let(:provider1) { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, redis_connection_pool, 'vsphere', provider_options) }
+ let(:provider2) { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, redis_connection_pool, 'secondvsphere', provider_options) }
let(:config) {
YAML.load(<<-EOT
---
@@ -2571,8 +2968,8 @@ EOT
it 'should call create_provider_object twice' do
# The two pools use a different provider name, but each provider_class is vsphere
- expect(subject).to receive(:create_provider_object).with(Object, Object, Object, "vsphere", "vsphere", Object).and_return(vsphere_provider)
- expect(subject).to receive(:create_provider_object).with(Object, Object, Object, "vsphere", "secondvsphere", Object).and_return(vsphere_provider2)
+ expect(subject).to receive(:create_provider_object).with(Object, Object, Object, redis_connection_pool, "vsphere", "vsphere", Object).and_return(vsphere_provider)
+ expect(subject).to receive(:create_provider_object).with(Object, Object, Object, redis_connection_pool, "vsphere", "secondvsphere", Object).and_return(vsphere_provider2)
subject.execute!(1,0)
end
@@ -2706,6 +3103,24 @@ EOT
end
end
+ context 'with dead ondemand provisioner thread' do
+ let(:ondemand_provisioner_thread) { double('thread', :alive? => false) }
+ let(:default_check_loop_delay_min) { 5 }
+ let(:default_check_loop_delay_max) { 60 }
+ let(:default_check_loop_delay_decay) { 2.0 }
+ before(:each) do
+ # Reset the global variable - Note this is a code smell
+ $threads = {}
+ $threads['ondemand_provisioner'] = ondemand_provisioner_thread
+ end
+
+ it 'should run the process_ondemand_requests method' do
+ expect(subject).to receive(:check_ondemand_requests).with(default_check_loop_delay_min, default_check_loop_delay_max, default_check_loop_delay_decay)
+ subject.execute!(1,0)
+ end
+
+ end
+
context 'with check_loop_delay_xxx settings' do
let(:pool_thread) { double('thread', :alive? => false) }
let(:check_loop_delay_min) { 7 }
@@ -2747,10 +3162,11 @@ EOT
let(:loop_delay) { 1 }
# Note a maxloop of zero can not be tested as it never terminates
before(:each) do
-
+
allow(subject).to receive(:check_disk_queue)
allow(subject).to receive(:check_snapshot_queue)
allow(subject).to receive(:check_pool)
+ allow(subject).to receive(:check_ondemand_requests)
end
it 'when a non-default loop delay is specified' do
@@ -2768,8 +3184,11 @@ EOT
end
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
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:set).with('vmpooler__tasks__clone', 0).once
+ expect(redis).to receive(:set).with('vmpooler__tasks__ondemandclone', 0).once
+ expect(redis).to receive(:del).with('vmpooler__migration').once
+ end
subject.execute!(maxloop,0)
end
@@ -2778,6 +3197,7 @@ EOT
expect(subject).to receive(:check_disk_queue).exactly(maxloop).times
expect(subject).to receive(:check_snapshot_queue).exactly(maxloop).times
expect(subject).to receive(:check_pool).exactly(maxloop).times
+ expect(subject).to receive(:check_ondemand_requests).exactly(maxloop).times
subject.execute!(maxloop,0)
end
@@ -2786,10 +3206,12 @@ EOT
expect(subject).to receive(:check_disk_queue).exactly(0).times
expect(subject).to receive(:check_snapshot_queue).exactly(0).times
expect(subject).to receive(:check_pool).exactly(0).times
+ expect(subject).to receive(:check_ondemand_requests).exactly(0).times
$threads[pool] = alive_thread
$threads['disk_manager'] = alive_thread
$threads['snapshot_manager'] = alive_thread
+ $threads['ondemand_provisioner'] = alive_thread
subject.execute!(maxloop,0)
end
@@ -2798,7 +3220,9 @@ EOT
context 'when redis server connection is not available' do
let(:maxloop) { 2 }
it 'should log a failure and raise the error' do
- expect(redis).to receive(:set).with('vmpooler__tasks__clone', 0).and_raise(Redis::CannotConnectError)
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:set).with('vmpooler__tasks__clone', 0).and_raise(Redis::CannotConnectError)
+ end
expect(logger).to receive(:log).with('s', 'Cannot connect to the redis server: Redis::CannotConnectError')
expect{subject.execute!(maxloop,0)}.to raise_error Redis::CannotConnectError
@@ -2838,22 +3262,28 @@ EOT
let(:wakeup_period) { -1 } # A negative number forces the wakeup evaluation to always occur
it 'should check the number of VMs ready in Redis' do
- expect(subject).to receive(:time_passed?).with(:exit_by, Time).and_return(false, true)
- expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").once
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:time_passed?).with(:exit_by, Time).and_return(false, true)
+ expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").once
+ end
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
end
it 'should sleep until the number of VMs ready in Redis increases' do
- expect(subject).to receive(:sleep).exactly(3).times
- expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(1,1,1,2)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:sleep).exactly(3).times
+ expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(1,1,1,2)
+ end
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
end
it 'should sleep until the number of VMs ready in Redis decreases' do
- expect(subject).to receive(:sleep).exactly(3).times
- expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(2,2,2,1)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:sleep).exactly(3).times
+ expect(redis).to receive(:scard).with("vmpooler__ready__#{pool}").and_return(2,2,2,1)
+ end
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
end
@@ -2869,20 +3299,26 @@ EOT
context 'with a template configured' do
before(:each) do
- redis.hset('vmpooler__config__template', pool, new_template)
- allow(redis).to receive(:hget)
+ redis_connection_pool.with do |redis|
+ redis.hset('vmpooler__config__template', pool, new_template)
+ allow(redis).to receive(:hget)
+ end
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
+ redis_connection_pool.with do |redis|
+ 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
+ end
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)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:sleep).exactly(3).times
+ expect(redis).to receive(:hget).with('vmpooler__config__template', pool).and_return(nil,nil,new_template)
+ end
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
end
@@ -2898,18 +3334,73 @@ EOT
let(:wakeup_period) { -1 } # A negative number forces the wakeup evaluation to always occur
context 'when a pool reset is requested' do
- before(:each) do
- redis.sadd('vmpooler__poolreset', pool)
- end
it 'should sleep until the reset request is detected' do
- expect(subject).to receive(:sleep).exactly(3).times
- expect(redis).to receive(:sismember).with('vmpooler__poolreset', pool).and_return(false,false,true)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:sleep).exactly(3).times
+ expect(redis).to receive(:sismember).with('vmpooler__poolreset', pool).and_return(false,false,true)
+ end
subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
end
end
end
+
+ describe 'with the pending_vm wakeup option' do
+ let(:wakeup_option) {{
+ :pending_vm => true,
+ :poolname => pool
+ }}
+
+ let(:wakeup_period) { -1 } # A negative number forces the wakeup evaluation to always occur
+
+ context 'when a pending_vm is detected' do
+
+ it 'should sleep until the pending instance' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:sleep).exactly(3).times
+ expect(redis).to receive(:scard).with("vmpooler__pending__#{pool}").and_return(0,0,1)
+ end
+
+ subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
+ end
+ end
+ end
+
+ describe 'with the ondemand_request wakeup option' do
+ let(:wakeup_option) {{ :ondemand_request => true }}
+
+ let(:wakeup_period) { -1 } # A negative number forces the wakeup evaluation to always occur
+
+ it 'should sleep until the provisioning request is detected' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:sleep).exactly(3).times
+ expect(redis).to receive(:multi).and_return('OK').exactly(3).times
+ expect(redis).to receive(:exec).and_return([0,0,0],[0,0,0],[1,0,0])
+ end
+
+ subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
+ end
+
+ it 'should sleep until provisioning processing is detected' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:sleep).exactly(3).times
+ expect(redis).to receive(:multi).and_return('OK').exactly(3).times
+ expect(redis).to receive(:exec).and_return([0,0,0],[0,0,0],[0,1,0])
+ end
+ subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
+ end
+
+ it 'should sleep until ondemand creation task is detected' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:sleep).exactly(3).times
+ expect(redis).to receive(:multi).and_return('OK').exactly(3).times
+ expect(redis).to receive(:exec).and_return([0,0,0],[0,0,0],[0,0,1])
+ end
+
+ subject.sleep_with_wakeup_events(loop_delay, wakeup_period, wakeup_option)
+ end
+ end
end
describe "#check_pool" do
@@ -3197,7 +3688,9 @@ EOT
{}
}
before(:each) do
- create_running_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ create_running_vm(pool,vm,redis,token)
+ end
end
it 'should not call check_running_vm' do
@@ -3207,7 +3700,9 @@ EOT
end
it 'should move the VM to completed queue' do
- expect(subject).to receive(:move_vm_queue).with(pool,vm,'running','completed',String).and_call_original
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:move_vm_queue).with(pool,vm,'running','completed',redis,String).and_call_original
+ end
subject.check_running_pool_vms(pool,provider, pool_check_response, inventory)
end
@@ -3220,8 +3715,10 @@ EOT
{ vm => 1 }
}
before(:each) do
- allow(subject).to receive(:check_running_vm)
- create_running_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ allow(subject).to receive(:check_running_vm)
+ create_running_vm(pool,vm,redis,token)
+ end
end
it 'should log an error if one occurs' do
@@ -3239,8 +3736,9 @@ EOT
it 'should use the VM lifetime in preference to defaults' do
big_lifetime = 2000
-
- redis.hset("vmpooler__vm__#{vm}", 'lifetime',big_lifetime)
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vm}", 'lifetime',big_lifetime)
+ end
# The lifetime comes in as string
expect(subject).to receive(:check_running_vm).with(vm,pool,"#{big_lifetime}",provider)
@@ -3264,6 +3762,7 @@ EOT
describe '#check_ready_pool_vms' do
let(:provider) { double('provider') }
+ let(:pool_ttl) { 5 }
let(:pool_check_response) {
{:checked_ready_vms => 0}
}
@@ -3274,19 +3773,23 @@ EOT
{}
}
before(:each) do
- create_ready_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ create_ready_vm(pool,vm,redis,token)
+ end
end
it 'should not call check_ready_vm' do
expect(subject).to receive(:check_ready_vm).exactly(0).times
- subject.check_ready_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_ready_pool_vms(pool, provider, pool_check_response, inventory, pool_ttl)
end
it 'should move the VM to completed queue' do
- expect(subject).to receive(:move_vm_queue).with(pool,vm,'ready','completed',String).and_call_original
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:move_vm_queue).with(pool,vm,'ready','completed',redis,String).and_call_original
+ end
- subject.check_ready_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_ready_pool_vms(pool, provider, pool_check_response, inventory, pool_ttl)
end
end
@@ -3297,12 +3800,14 @@ EOT
}
let(:big_lifetime) { 2000 }
before(:each) do
- allow(subject).to receive(:check_ready_vm)
- create_ready_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ allow(subject).to receive(:check_ready_vm)
+ create_ready_vm(pool,vm,redis,token)
+ end
end
it 'should return the number of checked ready VMs' do
- subject.check_ready_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_ready_pool_vms(pool, provider, pool_check_response, inventory, pool_ttl)
expect(pool_check_response[:checked_ready_vms]).to be(1)
end
@@ -3321,15 +3826,16 @@ EOT
end
it 'should use a pool TTL of zero if none set' do
- expect(subject).to receive(:check_ready_vm).with(vm,pool,0,provider)
+ expect(subject).to receive(:check_ready_vm).with(vm,pool,pool_ttl,provider)
- subject.check_ready_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_ready_pool_vms(pool, provider, pool_check_response, inventory, pool_ttl)
end
end
end
describe '#check_pending_pool_vms' do
let(:provider) { double('provider') }
+ let(:timeout) { 10 }
let(:pool_check_response) {
{:checked_pending_vms => 0}
}
@@ -3341,13 +3847,17 @@ EOT
}
before(:each) do
- create_pending_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ create_pending_vm(pool,vm,redis,token)
+ end
end
it 'should call fail_pending_vm' do
- expect(subject).to receive(:fail_pending_vm).with(vm,pool,Integer,false)
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:fail_pending_vm).with(vm,pool,Integer,redis,false)
+ end
- subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory, timeout)
end
end
@@ -3358,12 +3868,14 @@ EOT
}
before(:each) do
- create_pending_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ create_pending_vm(pool,vm,redis,token)
+ end
end
it 'should return the number of checked pending VMs' do
allow(subject).to receive(:check_pending_vm)
- subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory, timeout)
expect(pool_check_response[:checked_pending_vms]).to be(1)
end
@@ -3372,7 +3884,7 @@ EOT
expect(subject).to receive(:check_pending_vm).and_raise(RuntimeError,'MockError')
expect(logger).to receive(:log).with('d', "[!] [#{pool}] _check_pool failed with an error while evaluating pending VMs: MockError")
- subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory, timeout)
end
it 'should use the pool timeout if set' do
@@ -3390,13 +3902,13 @@ EOT
config[:config]['timeout'] = big_lifetime
expect(subject).to receive(:check_pending_vm).with(vm,pool,big_lifetime,provider)
- subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory, big_lifetime)
end
it 'should use a pool timeout of 15 if nothing is set' do
- expect(subject).to receive(:check_pending_vm).with(vm,pool,15,provider)
+ expect(subject).to receive(:check_pending_vm).with(vm,pool,timeout,provider)
- subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_pending_pool_vms(pool, provider, pool_check_response, inventory, timeout)
end
end
end
@@ -3415,7 +3927,9 @@ EOT
}
before(:each) do
- create_completed_vm(vm,pool,true)
+ redis_connection_pool.with do |redis|
+ create_completed_vm(vm,pool,redis,true)
+ end
end
it 'should log a message' do
@@ -3429,15 +3943,17 @@ EOT
end
it 'should remove redis information' do
- expect(redis.sismember("vmpooler__completed__#{pool}",vm)).to be(true)
- expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to_not be(nil)
- expect(redis.hget("vmpooler__active__#{pool}",vm)).to_not be(nil)
+ redis_connection_pool.with do |redis|
+ expect(redis.sismember("vmpooler__completed__#{pool}",vm)).to be(true)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to_not be(nil)
+ expect(redis.hget("vmpooler__active__#{pool}",vm)).to_not be(nil)
- subject.check_completed_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_completed_pool_vms(pool, provider, pool_check_response, inventory)
- expect(redis.sismember("vmpooler__completed__#{pool}",vm)).to be(false)
- expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to be(nil)
- expect(redis.hget("vmpooler__active__#{pool}",vm)).to be(nil)
+ expect(redis.sismember("vmpooler__completed__#{pool}",vm)).to be(false)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to be(nil)
+ expect(redis.hget("vmpooler__active__#{pool}",vm)).to be(nil)
+ end
end
end
@@ -3448,7 +3964,9 @@ EOT
}
before(:each) do
- create_completed_vm(vm,pool,true)
+ redis_connection_pool.with do |redis|
+ create_completed_vm(vm,pool,redis,true)
+ end
end
it 'should call destroy_vm' do
@@ -3474,15 +3992,17 @@ EOT
end
it 'should remove redis information' do
- expect(redis.sismember("vmpooler__completed__#{pool}",vm)).to be(true)
- expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to_not be(nil)
- expect(redis.hget("vmpooler__active__#{pool}",vm)).to_not be(nil)
+ redis_connection_pool.with do |redis|
+ expect(redis.sismember("vmpooler__completed__#{pool}",vm)).to be(true)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to_not be(nil)
+ expect(redis.hget("vmpooler__active__#{pool}",vm)).to_not be(nil)
- subject.check_completed_pool_vms(pool, provider, pool_check_response, inventory)
+ subject.check_completed_pool_vms(pool, provider, pool_check_response, inventory)
- expect(redis.sismember("vmpooler__completed__#{pool}",vm)).to be(false)
- expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to be(nil)
- expect(redis.hget("vmpooler__active__#{pool}",vm)).to be(nil)
+ expect(redis.sismember("vmpooler__completed__#{pool}",vm)).to be(false)
+ expect(redis.hget("vmpooler__vm__#{vm}", 'checkout')).to be(nil)
+ expect(redis.hget("vmpooler__active__#{pool}",vm)).to be(nil)
+ end
end
end
end
@@ -3491,17 +4011,23 @@ EOT
describe "#check_discovered_pool_vms" do
context 'Discovered VM' do
before(:each) do
- create_discovered_vm(vm,pool)
+ redis_connection_pool.with do |redis|
+ create_discovered_vm(vm,pool,redis)
+ end
end
it 'should be moved to the completed queue' do
subject.check_discovered_pool_vms(pool)
- expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
+ redis_connection_pool.with do |redis|
+ expect(redis.sismember("vmpooler__completed__#{pool}", vm)).to be(true)
+ end
end
it 'should log a message if an error occurs' do
- expect(redis).to receive(:smove).with("vmpooler__discovered__#{pool}", "vmpooler__completed__#{pool}", vm).and_raise(RuntimeError,'MockError')
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:smove).with("vmpooler__discovered__#{pool}", "vmpooler__completed__#{pool}", vm).and_raise(RuntimeError,'MockError')
+ end
expect(logger).to receive(:log).with("d", "[!] [#{pool}] _check_pool failed with an error while evaluating discovered VMs: MockError")
subject.check_discovered_pool_vms(pool)
@@ -3519,25 +4045,31 @@ EOT
end
it "should remain in the #{queue_name} queue" do
- redis.sadd("vmpooler__#{queue_name}__#{pool}", vm)
- allow(logger).to receive(:log)
+ redis_connection_pool.with do |redis|
+ redis.sadd("vmpooler__#{queue_name}__#{pool}", vm)
+ allow(logger).to receive(:log)
- subject.check_discovered_pool_vms(pool)
+ subject.check_discovered_pool_vms(pool)
- expect(redis.sismember("vmpooler__#{queue_name}__#{pool}", vm)).to be(true)
+ expect(redis.sismember("vmpooler__#{queue_name}__#{pool}", vm)).to be(true)
+ end
end
it "should be removed from the discovered queue" do
- redis.sadd("vmpooler__#{queue_name}__#{pool}", vm)
- allow(logger).to receive(:log)
+ redis_connection_pool.with do |redis|
+ redis.sadd("vmpooler__#{queue_name}__#{pool}", vm)
+ allow(logger).to receive(:log)
- expect(redis.sismember("vmpooler__discovered__#{pool}", vm)).to be(true)
- subject.check_discovered_pool_vms(pool)
- expect(redis.sismember("vmpooler__discovered__#{pool}", vm)).to be(false)
+ expect(redis.sismember("vmpooler__discovered__#{pool}", vm)).to be(true)
+ subject.check_discovered_pool_vms(pool)
+ expect(redis.sismember("vmpooler__discovered__#{pool}", vm)).to be(false)
+ end
end
it "should log a message" do
- redis.sadd("vmpooler__#{queue_name}__#{pool}", vm)
+ redis_connection_pool.with do |redis|
+ redis.sadd("vmpooler__#{queue_name}__#{pool}", vm)
+ end
expect(logger).to receive(:log).with('d', "[!] [#{pool}] '#{vm}' found in '#{queue_name}', removed from 'discovered' queue")
subject.check_discovered_pool_vms(pool)
@@ -3561,7 +4093,9 @@ EOT
}
before(:each) do
- create_migrating_vm(vm,pool)
+ redis_connection_pool.with do |redis|
+ create_migrating_vm(vm,pool,redis)
+ end
end
it 'should not do anything' do
@@ -3578,7 +4112,9 @@ EOT
}
before(:each) do
- create_migrating_vm(vm,pool)
+ redis_connection_pool.with do |redis|
+ create_migrating_vm(vm,pool,redis)
+ end
end
it 'should return the number of migrated VMs' do
@@ -3604,7 +4140,7 @@ EOT
end
describe '#repopulate_pool_vms' do
- let(:pool_size) { 0 }
+ let(:pool_size) { 1 }
let(:config) {
YAML.load(<<-EOT
---
@@ -3623,11 +4159,13 @@ EOT
it 'should not call clone_vm when number of VMs is equal to the pool size' do
expect(subject).to receive(:clone_vm).exactly(0).times
- subject.repopulate_pool_vms(pool, provider, pool_check_response, pool_size)
+ subject.repopulate_pool_vms(pool, provider, pool_check_response, 0)
end
it 'should not call clone_vm when number of VMs is greater than the pool size' do
- create_ready_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ create_ready_vm(pool,vm,redis,token)
+ end
expect(subject).to receive(:clone_vm).exactly(0).times
subject.repopulate_pool_vms(pool, provider, pool_check_response, pool_size)
@@ -3637,9 +4175,11 @@ EOT
it "should use VMs in #{queue_name} queue to calculate pool size" do
expect(subject).to receive(:clone_vm).exactly(0).times
# Modify the pool size to 1 and add a VM in the queue
- redis.sadd("vmpooler__#{queue_name}__#{pool}",vm)
+ redis_connection_pool.with do |redis|
+ redis.sadd("vmpooler__#{queue_name}__#{pool}",vm)
+ end
pool_size = 1
-
+
subject.repopulate_pool_vms(pool, provider, pool_check_response, pool_size)
end
end
@@ -3648,7 +4188,9 @@ EOT
it "should not use VMs in #{queue_name} queue to calculate pool size" do
expect(subject).to receive(:clone_vm)
# Modify the pool size to 1 and add a VM in the queue
- redis.sadd("vmpooler__#{queue_name}__#{pool}",vm)
+ redis_connection_pool.with do |redis|
+ redis.sadd("vmpooler__#{queue_name}__#{pool}",vm)
+ end
pool_size = 1
subject.repopulate_pool_vms(pool, provider, pool_check_response, pool_size)
@@ -3661,10 +4203,18 @@ EOT
subject.repopulate_pool_vms(pool, provider, pool_check_response, pool_size)
end
+ it 'should not log a message when the pool size is 0' do
+ expect(logger).to_not receive(:log).with('s', "[!] [#{pool}] is empty")
+
+ subject.repopulate_pool_vms(pool, provider, pool_check_response, 0)
+ end
+
context 'when pool is marked as empty' do
before(:each) do
- redis.set("vmpooler__empty__#{pool}", 'true')
+ redis_connection_pool.with do |redis|
+ redis.set("vmpooler__empty__#{pool}", 'true')
+ end
end
it 'should not log a message when the pool remains empty' do
@@ -3674,11 +4224,13 @@ EOT
end
it 'should remove the empty pool mark if it is no longer empty' do
- create_ready_vm(pool,vm,token)
+ redis_connection_pool.with do |redis|
+ create_ready_vm(pool,vm,redis,token)
- expect(redis.get("vmpooler__empty__#{pool}")).to be_truthy
- subject.repopulate_pool_vms(pool, provider, pool_check_response, pool_size)
- expect(redis.get("vmpooler__empty__#{pool}")).to be_falsey
+ expect(redis.get("vmpooler__empty__#{pool}")).to be_truthy
+ subject.repopulate_pool_vms(pool, provider, pool_check_response, pool_size)
+ expect(redis.get("vmpooler__empty__#{pool}")).to be_falsey
+ end
end
end
@@ -3711,11 +4263,13 @@ EOT
end
it 'log a message if a cloning error occurs' do
+ redis_connection_pool.with do |redis|
+ create_ready_vm(pool,'vm',redis)
+ end
pool_size = 2
expect(subject).to receive(:clone_vm).and_raise(RuntimeError,"MockError")
expect(logger).to receive(:log).with("s", "[!] [#{pool}] clone failed during check_pool with an error: MockError")
- create_ready_vm(pool,'vm')
expect{ subject.repopulate_pool_vms(pool, provider, pool_check_response, pool_size) }.to raise_error(RuntimeError,'MockError')
end
@@ -3738,9 +4292,11 @@ EOT
context 'export metrics' do
it 'increments metrics for ready queue' do
- create_ready_vm(pool,'vm1')
- create_ready_vm(pool,'vm2')
- create_ready_vm(pool,'vm3')
+ redis_connection_pool.with do |redis|
+ create_ready_vm(pool,'vm1',redis)
+ create_ready_vm(pool,'vm2',redis)
+ create_ready_vm(pool,'vm3',redis)
+ end
expect(metrics).to receive(:gauge).with("ready.#{pool}", 3)
allow(metrics).to receive(:gauge)
@@ -3749,9 +4305,11 @@ EOT
end
it 'increments metrics for running queue' do
- create_running_vm(pool,'vm1',token)
- create_running_vm(pool,'vm2',token)
- create_running_vm(pool,'vm3',token)
+ redis_connection_pool.with do |redis|
+ create_running_vm(pool,'vm1',redis,token)
+ create_running_vm(pool,'vm2',redis,token)
+ create_running_vm(pool,'vm3',redis,token)
+ end
expect(metrics).to receive(:gauge).with("running.#{pool}", 3)
allow(metrics).to receive(:gauge)
@@ -3761,6 +4319,7 @@ EOT
it 'increments metrics with 0 when pool empty' do
+ allow(metrics).to receive(:gauge)
expect(metrics).to receive(:gauge).with("ready.#{pool}", 0)
expect(metrics).to receive(:gauge).with("running.#{pool}", 0)
@@ -3870,30 +4429,34 @@ EOT
end
it 'should add undiscovered VMs to the completed queue' do
- allow(logger).to receive(:log).with('s', "[?] [#{pool}] '#{new_vm}' added to 'discovered' queue")
+ redis_connection_pool.with do |redis|
+ allow(logger).to receive(:log).with('s', "[?] [#{pool}] '#{new_vm}' added to 'discovered' queue")
- expect(redis.sismember("vmpooler__discovered__#{pool}", new_vm)).to be(false)
- expect(redis.sismember("vmpooler__completed__#{pool}", new_vm)).to be(false)
+ expect(redis.sismember("vmpooler__discovered__#{pool}", new_vm)).to be(false)
+ expect(redis.sismember("vmpooler__completed__#{pool}", new_vm)).to be(false)
- subject._check_pool(pool_object,provider)
+ subject._check_pool(pool_object,provider)
- expect(redis.sismember("vmpooler__discovered__#{pool}", new_vm)).to be(false)
- expect(redis.sismember("vmpooler__completed__#{pool}", new_vm)).to be(true)
+ expect(redis.sismember("vmpooler__discovered__#{pool}", new_vm)).to be(false)
+ expect(redis.sismember("vmpooler__completed__#{pool}", new_vm)).to be(true)
+ end
end
['running','ready','pending','completed','discovered','migrating'].each do |queue_name|
it "should not discover VMs in the #{queue_name} queue" do
- expect(logger).to receive(:log).with('s', "[?] [#{pool}] '#{new_vm}' added to 'discovered' queue").exactly(0).times
- expect(redis.sismember("vmpooler__discovered__#{pool}", new_vm)).to be(false)
- redis.sadd("vmpooler__#{queue_name}__#{pool}", new_vm)
+ redis_connection_pool.with do |redis|
+ expect(logger).to receive(:log).with('s', "[?] [#{pool}] '#{new_vm}' added to 'discovered' queue").exactly(0).times
+ expect(redis.sismember("vmpooler__discovered__#{pool}", new_vm)).to be(false)
+ redis.sadd("vmpooler__#{queue_name}__#{pool}", new_vm)
- subject._check_pool(pool_object,provider)
+ subject._check_pool(pool_object,provider)
- if queue_name == 'discovered'
- # Discovered VMs end up in the completed queue
- expect(redis.sismember("vmpooler__completed__#{pool}", new_vm)).to be(true)
- else
- expect(redis.sismember("vmpooler__#{queue_name}__#{pool}", new_vm)).to be(true)
+ if queue_name == 'discovered'
+ # Discovered VMs end up in the completed queue
+ expect(redis.sismember("vmpooler__completed__#{pool}", new_vm)).to be(true)
+ else
+ expect(redis.sismember("vmpooler__#{queue_name}__#{pool}", new_vm)).to be(true)
+ end
end
end
end
@@ -4059,7 +4622,9 @@ EOT
let(:newpoolsize) { 3 }
before(:each) do
config[:pools][0]['size'] = poolsize
- redis.hset('vmpooler__config__poolsize', pool, newpoolsize)
+ redis_connection_pool.with do |redis|
+ redis.hset('vmpooler__config__poolsize', pool, newpoolsize)
+ end
end
it 'should change the pool size configuration' do
@@ -4096,9 +4661,11 @@ EOT
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)
+ redis_connection_pool.with do |redis|
+ 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)
+ end
end
it 'should call remove_excess_vms' do
@@ -4108,9 +4675,445 @@ EOT
subject._check_pool(config[:pools][0],provider)
end
end
+ end
- #
+ describe 'process_ondemand_requests' do
+ context 'with no requests' do
+ it 'returns 0' do
+ result = subject.process_ondemand_requests
+ expect(result).to eq(0)
+ end
+ it 'runs process_ondemand_vms' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:process_ondemand_vms).with(redis).and_return(0)
+ subject.process_ondemand_requests
+ end
+ end
+ it 'checks ready requests' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:check_ondemand_requests_ready).with(redis).and_return(0)
+ subject.process_ondemand_requests
+ end
+ end
+ end
+
+ context 'with provisioning requests' do
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ redis.zadd('vmpooler__provisioning__request', current_time, request_id)
+ end
+ end
+
+ it 'returns the number of requests processed' do
+ result = subject.process_ondemand_requests
+ expect(result).to eq(1)
+ end
+
+ it 'runs create_ondemand_vms for each request' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:create_ondemand_vms).with(request_id, redis)
+ subject.process_ondemand_requests
+ end
+ end
+ end
+ end
+
+ describe '#create_ondemand_vms' do
+ context 'when requested does not have corresponding data' do
+ it 'logs an error' do
+ redis_connection_pool.with do |redis|
+ expect(logger).to receive(:log).with('s', "Failed to find odrequest for request_id '1111'")
+ subject.create_ondemand_vms('1111', redis)
+ end
+ end
+ end
+
+ context 'with a request that has data' do
+ let(:request_string) { "#{pool}:#{pool}:1" }
+ before(:each) do
+ expect(Time).to receive(:now).and_return(current_time).at_least(:once)
+ redis_connection_pool.with do |redis|
+ create_ondemand_request_for_test(request_id, current_time.to_i, request_string, redis)
+ end
+ end
+
+ it 'creates tasks for instances to be provisioned' do
+ redis_connection_pool.with do |redis|
+ allow(redis).to receive(:zadd)
+ expect(redis).to receive(:zadd).with('vmpooler__odcreate__task', current_time.to_i, "#{request_string}:#{request_id}")
+ subject.create_ondemand_vms(request_id, redis)
+ end
+ end
+
+ it 'adds a member to provisioning__processing' do
+ redis_connection_pool.with do |redis|
+ allow(redis).to receive(:zadd)
+ expect(redis).to receive(:zadd).with('vmpooler__provisioning__processing', current_time.to_i, request_id)
+ subject.create_ondemand_vms(request_id, redis)
+ end
+ end
+ end
+ end
+
+ describe '#process_ondemand_vms' do
+ it 'returns the length of the queue' do
+ redis_connection_pool.with do |redis|
+ result = subject.process_ondemand_vms(redis)
+ expect(result).to eq(0)
+ end
+ end
+
+ context 'with a request to create a single vm' do
+ let(:request_string) { "#{pool}:#{pool}:1" }
+ let(:pool_alias) { pool }
+ before(:each) do
+ config[:config]['ondemand_clone_limit'] = 10
+ expect(subject).to receive(:get_provider_for_pool).and_return(provider)
+ redis_connection_pool.with do |redis|
+ create_ondemand_creationtask("#{request_string}:#{request_id}", current_time.to_i, redis)
+ end
+ end
+
+ it 'creates the vm' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:clone_vm).with(pool, provider, request_id, pool_alias)
+ subject.process_ondemand_vms(redis)
+ end
+ end
+ end
+
+ context 'with a request to create more instances than the limit' do
+ let(:request_string) { "#{pool}:#{pool}:5" }
+ let(:request_string_remaining) { "#{pool}:#{pool}:2" }
+ let(:pool_alias) { pool }
+ before(:each) do
+ config[:config]['ondemand_clone_limit'] = 3
+ expect(subject).to receive(:get_provider_for_pool).and_return(provider)
+ redis_connection_pool.with do |redis|
+ create_ondemand_creationtask("#{request_string}:#{request_id}", current_time.to_i, redis)
+ end
+ end
+
+ it 'should create the maximum number of vms' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:clone_vm).with(pool, provider, request_id, pool_alias).exactly(3).times
+ subject.process_ondemand_vms(redis)
+ end
+ end
+
+ it 'should add the remaining number back as a new create task with the same score' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:zadd).with('vmpooler__odcreate__task', current_time.to_i, "#{request_string_remaining}:#{request_id}")
+ subject.process_ondemand_vms(redis)
+ end
+ end
+
+ it 'should return the number of requests processed' do
+ redis_connection_pool.with do |redis|
+ result = subject.process_ondemand_vms(redis)
+ expect(result).to eq(1)
+ end
+ end
+ end
+
+ context 'when the limit has been reached' do
+ let(:clone_count) { { 'ondemand_clone_count' => 3 } }
+ before(:each) do
+ config[:config]['ondemand_clone_limit'] = 3
+ subject.instance_variable_set(:@tasks, clone_count)
+ end
+
+ it 'does not create any instances' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to_not receive(:clone_vm)
+ subject.process_ondemand_vms(redis)
+ end
+ end
+ end
+ end
+
+ describe '#vms_ready?' do
+ let(:request_string) { "#{pool}:#{pool}:5" }
+ let(:platform_alias) { pool }
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ create_ondemand_request_for_test(request_id, current_time.to_i, request_string, redis)
+ end
+ end
+
+ it 'returns false when vms for request_id are not ready' do
+ redis_connection_pool.with do |redis|
+ result = subject.vms_ready?(request_id, redis)
+ expect(result).to be false
+ end
+ end
+
+ context 'with a request that has all instances ready' do
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ ['vm1','vm2','vm3','vm4','vm5'].each do |v|
+ redis.sadd("vmpooler__#{request_id}__#{platform_alias}__#{pool}", v)
+ end
+ end
+ end
+
+ it 'returns true' do
+ redis_connection_pool.with do |redis|
+ result = subject.vms_ready?(request_id, redis)
+ expect(result).to be true
+ end
+ end
+ end
+
+ context 'with a request that has some instances ready' do
+ let(:request_string) { "#{pool}:#{pool}:3,#{pool}2:#{pool}2:3" }
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ create_ondemand_request_for_test(request_id, current_time.to_i, request_string, redis)
+ ['vm1','vm2','vm3'].each do |v|
+ redis.sadd("vmpooler__#{request_id}__#{platform_alias}__#{pool}", v)
+ end
+ end
+ end
+
+ it 'returns false' do
+ redis_connection_pool.with do |redis|
+ result = subject.vms_ready?(request_id, redis)
+ expect(result).to be false
+ end
+ end
+ end
+ end
+
+ describe '#check_ondemand_requests_ready' do
+
+ before(:each) do
+ config[:config]['ondemand_request_ttl'] = 5
+ end
+
+ it 'returns 0 when no provisoning requests are in progress' do
+ redis_connection_pool.with do |redis|
+ result = subject.check_ondemand_requests_ready(redis)
+ expect(result).to eq(0)
+ end
+ end
+
+ context 'with requests in progress' do
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ create_ondemand_processing(request_id, current_time, redis)
+ end
+ end
+
+ it 'returns the number of requests processed' do
+ expect(subject).to receive(:vms_ready?).and_return(false)
+ redis_connection_pool.with do |redis|
+ result = subject.check_ondemand_requests_ready(redis)
+ expect(result).to eq(1)
+ end
+ end
+
+ context 'when the request is ready' do
+ before(:each) do
+ expect(subject).to receive(:vms_ready?).and_return(true)
+ end
+
+ it 'sets the request as ready' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hset).with("vmpooler__odrequest__#{request_id}", 'status', 'ready')
+ subject.check_ondemand_requests_ready(redis)
+ end
+ end
+
+ it 'marks the ondemand request hash key for expiration in one month' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:expire).with("vmpooler__odrequest__#{request_id}", 2592000)
+ subject.check_ondemand_requests_ready(redis)
+ end
+ end
+
+ it 'removes the request from processing' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:zrem).with('vmpooler__provisioning__processing', request_id)
+ subject.check_ondemand_requests_ready(redis)
+ end
+ end
+ end
+
+ context 'when a request has taken too long to be filled' do
+ it 'should return true for request_expired?' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:request_expired?).with(request_id, Float, redis).and_return(true)
+ subject.check_ondemand_requests_ready(redis)
+ end
+ end
+ end
+ end
+ end
+
+ describe '#request_expired?' do
+ let(:ondemand_request_ttl) { 5 }
+ let(:expiration_ttl) { 10 }
+ before(:each) do
+ config[:config]['ondemand_request_ttl'] = ondemand_request_ttl
+ config[:redis]['data_ttl'] = expiration_ttl
+ end
+
+ context 'with a request that has taken too long to be filled' do
+ let(:expired_time) { (Time.now - 960).to_i }
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:remove_vms_for_failed_request)
+ create_ondemand_processing(request_id, expired_time, redis)
+ end
+ end
+
+ it 'returns true when the request is expired' do
+ redis_connection_pool.with do |redis|
+ result = subject.request_expired?(request_id, expired_time, redis)
+ expect(result).to be true
+ end
+ end
+
+ it 'logs a message that the request has expired' do
+ redis_connection_pool.with do |redis|
+ expect(logger).to receive(:log).with('s', "Ondemand request for '#{request_id}' failed to provision all instances within the configured ttl '#{ondemand_request_ttl}'")
+ subject.request_expired?(request_id, expired_time, redis)
+ end
+ end
+
+ it 'removes the request from processing requests' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:zrem).with('vmpooler__provisioning__processing', request_id)
+ subject.request_expired?(request_id, expired_time, redis)
+ end
+ end
+
+ it 'sets the status as failed on the request hash' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:hset).with("vmpooler__odrequest__#{request_id}", 'status', 'failed')
+ subject.request_expired?(request_id, expired_time, redis)
+ end
+ end
+
+ it 'marks the request hash for expiration' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:expire).with("vmpooler__odrequest__#{request_id}", expiration_ttl * 60 * 60)
+ subject.request_expired?(request_id, expired_time, redis)
+ end
+ end
+ end
+
+ context 'with a request that has been made within the ttl' do
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ create_ondemand_processing(request_id, current_time, redis)
+ end
+ end
+
+ it 'should return false' do
+ redis_connection_pool.with do |redis|
+ result = subject.request_expired?(request_id, current_time, redis)
+ expect(result).to be false
+ end
+ end
+ end
+ end
+
+ describe '#remove_vms_for_failed_request)' do
+ let(:expiration_ttl) { 100 * 60 * 60 }
+ let(:platform_alias) { pool }
+ let(:platforms_string) { "#{platform_alias}:#{pool}:3" }
+ context 'with two vms marked as ready for the request' do
+ before(:each) do
+ redis_connection_pool.with do |redis|
+ create_ondemand_request_for_test(request_id, current_time, platforms_string, redis)
+ [vm,"#{vm}2"].each do |v|
+ create_running_vm(pool, v, redis)
+ redis.sadd("vmpooler__#{request_id}__#{platform_alias}__#{pool}", v)
+ end
+ end
+ end
+
+ it 'should remove the associated vms' do
+ redis_connection_pool.with do |redis|
+ expect(subject).to receive(:move_vm_queue).with(pool, String, 'running', 'completed', redis, "moved to completed queue. '#{request_id}' could not be filled in time").twice
+ subject.remove_vms_for_failed_request(request_id, expiration_ttl, redis)
+ end
+ end
+
+ it 'should mark the ready set for expiration' do
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:expire).with("vmpooler__#{request_id}__#{platform_alias}__#{pool}", expiration_ttl)
+ subject.remove_vms_for_failed_request(request_id, expiration_ttl, redis)
+ end
+ end
+ end
+ end
+
+ describe 'check_ondemand_requests' do
+ let(:threads) {[]}
+ let(:maxloop) { 0 }
+ let(:loop_delay_min) { 5 }
+ let(:loop_delay_max) { 60 }
+ let(:loop_delay_decay) { 2.0 }
+
+ before(:each) do
+ expect(Thread).to receive(:new).and_yield
+ end
+
+ it 'should log the ondemand provisioner is starting' do
+ expect(subject).to receive(:_check_ondemand_requests).with(maxloop, loop_delay_min, loop_delay_max, loop_delay_decay)
+ expect(logger).to receive(:log).with('d', "[*] [ondemand_provisioner] starting worker thread")
+
+ expect($threads.count).to be(0)
+ subject.check_ondemand_requests
+ expect($threads.count).to be(1)
+ end
+
+ context' delays between loops' do
+ let(:maxloop) { 2 }
+ let(:loop_delay) { 1 }
+
+ it 'when a non-default loop delay is specified' do
+ expect(subject).to receive(:sleep_with_wakeup_events).with(loop_delay, Numeric, Hash).exactly(maxloop).times
+
+ subject.check_ondemand_requests(maxloop,loop_delay,loop_delay)
+ end
+ end
+
+ context 'delays between loops with a specified min and max value' do
+ let(:maxloop) { 5 }
+ let(:loop_delay_min) { 1 }
+ let(:loop_delay_max) { 60 }
+ let(:loop_decay) { 3.0 }
+
+ it 'delay values increase with a decay' do
+ expect(subject).to receive(:sleep_with_wakeup_events).with(3, Numeric, Hash).once
+ expect(subject).to receive(:sleep_with_wakeup_events).with(9, Numeric, Hash).once
+ expect(subject).to receive(:sleep_with_wakeup_events).with(27, Numeric, Hash).once
+ expect(subject).to receive(:sleep_with_wakeup_events).with(60, Numeric, Hash).twice
+
+ subject.check_ondemand_requests(maxloop,loop_delay_min,loop_delay_max,loop_decay)
+ end
+ end
+
+ context 'loops specified number of times (5)' do
+ let(:maxloop) { 5 }
+
+ it 'should run startup tasks only once' do
+ expect(logger).to receive(:log).with('d', "[*] [ondemand_provisioner] starting worker thread")
+
+ subject.check_ondemand_requests(maxloop,0)
+ end
+
+ it 'should run per thread tasks 5 times' do
+ expect(subject).to receive(:process_ondemand_requests).and_return(0).exactly(maxloop).times
+
+ subject.check_ondemand_requests(maxloop,0)
+ end
+ end
end
end
diff --git a/spec/unit/providers/base_spec.rb b/spec/unit/providers/base_spec.rb
index 5dc71c5..27b3d94 100644
--- a/spec/unit/providers/base_spec.rb
+++ b/spec/unit/providers/base_spec.rb
@@ -22,7 +22,9 @@ describe 'Vmpooler::PoolManager::Provider::Base' do
fake_vm
}
- subject { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, provider_name, provider_options) }
+ let(:redis_connection_pool) { ConnectionPool.new(size: 1) { MockRedis.new } }
+
+ subject { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, redis_connection_pool, provider_name, provider_options) }
# Helper attr_reader methods
describe '#logger' do
diff --git a/spec/unit/providers/dummy_spec.rb b/spec/unit/providers/dummy_spec.rb
index 13006ce..a22d2e6 100644
--- a/spec/unit/providers/dummy_spec.rb
+++ b/spec/unit/providers/dummy_spec.rb
@@ -91,7 +91,9 @@ EOT
)
}
- subject { Vmpooler::PoolManager::Provider::Dummy.new(config, logger, metrics, 'dummy', provider_options) }
+ let(:redis_connection_pool) { ConnectionPool.new(size: 1) { MockRedis.new } }
+
+ subject { Vmpooler::PoolManager::Provider::Dummy.new(config, logger, metrics, redis_connection_pool, 'dummy', provider_options) }
describe '#name' do
it 'should be dummy' do
diff --git a/spec/unit/providers/vsphere_spec.rb b/spec/unit/providers/vsphere_spec.rb
index a4ce37a..4cfaeeb 100644
--- a/spec/unit/providers/vsphere_spec.rb
+++ b/spec/unit/providers/vsphere_spec.rb
@@ -77,9 +77,15 @@ EOT
let(:connection_options) {{}}
let(:connection) { mock_RbVmomi_VIM_Connection(connection_options) }
let(:vmname) { 'vm1' }
- let(:redis) { MockRedis.new }
+ let(:redis_connection_pool) { Vmpooler::PoolManager::GenericConnectionPool.new(
+ metrics: metrics,
+ metric_prefix: 'redis_connection_pool',
+ size: 1,
+ timeout: 5
+ ) { MockRedis.new }
+ }
- subject { Vmpooler::PoolManager::Provider::VSphere.new(config, logger, metrics, 'vsphere', provider_options) }
+ subject { Vmpooler::PoolManager::Provider::VSphere.new(config, logger, metrics, redis_connection_pool, 'vsphere', provider_options) }
before(:each) do
allow(subject).to receive(:vsphere_connection_ok?).and_return(true)
@@ -149,26 +155,19 @@ EOT
allow(destroy_task).to receive(:wait_for_completion)
allow(vm_object).to receive(:PowerOffVM_Task).and_return(power_off_task)
allow(vm_object).to receive(:Destroy_Task).and_return(destroy_task)
- $redis = redis
- end
-
- it 'should remove redis data and expire the vm key' do
- allow(Time).to receive(:now).and_return(now)
- expect(redis).to receive(:srem).with("vmpooler__completed__#{pool}", vmname)
- expect(redis).to receive(:hdel).with("vmpooler__active__#{pool}", vmname)
- expect(redis).to receive(:hset).with("vmpooler__vm__#{vmname}", 'destroy', now)
- expect(redis).to receive(:expire).with("vmpooler__vm__#{vmname}", data_ttl * 60 * 60)
-
- subject.destroy_vm_and_log(vmname, vm_object, pool, data_ttl)
end
it 'should log a message that the vm is destroyed' do
+ # Ensure Time returns a consistent value so finish is predictable
+ # Otherwise finish occasionally increases to 0.01 and causes a failure
+ allow(Time).to receive(:now).and_return(Time.now)
expect(logger).to receive(:log).with('s', "[-] [#{pool}] '#{vmname}' destroyed in #{finish} seconds")
subject.destroy_vm_and_log(vmname, vm_object, pool, data_ttl)
end
it 'should record metrics' do
+ expect(metrics).to receive(:timing).with('redis_connection_pool.waited', 0)
expect(metrics).to receive(:timing).with("destroy.#{pool}", finish)
subject.destroy_vm_and_log(vmname, vm_object, pool, data_ttl)
@@ -2573,10 +2572,12 @@ EOT
subject.connection_pool.with_metrics do |pool_object|
expect(subject).to receive(:ensured_vsphere_connection).with(pool_object).and_return(connection)
expect(subject).to receive(:get_vm_details).and_return(vm_details)
- allow($redis).to receive(:hset)
expect(subject).to receive(:run_select_hosts).with(poolname, {})
expect(subject).to receive(:vm_in_target?).and_return false
expect(subject).to receive(:migration_enabled?).and_return true
+ redis_connection_pool.with do |redis|
+ redis.hset("vmpooler__vm__#{vmname}", 'checkout', Time.now)
+ end
end
vm_object.summary.runtime.host = host_object
end
@@ -2585,7 +2586,6 @@ EOT
expect(subject).to receive(:select_next_host).and_return(new_host)
expect(subject).to receive(:find_host_by_dnsname).and_return(new_host_object)
expect(subject).to receive(:migrate_vm_host).with(vm_object, new_host_object)
- expect($redis).to receive(:hget).with("vmpooler__vm__#{vmname}", 'checkout').and_return((Time.now - 1).to_s)
expect(logger).to receive(:log).with('s', "[>] [#{poolname}] '#{vmname}' migrated from #{parent_host} to #{new_host} in 0.00 seconds")
subject.migrate_vm(poolname, vmname)
@@ -2603,7 +2603,6 @@ EOT
subject.connection_pool.with_metrics do |pool_object|
expect(subject).to receive(:ensured_vsphere_connection).with(pool_object).and_return(connection)
expect(subject).to receive(:get_vm_details).and_return(vm_details)
- expect($redis).to receive(:hset).with("vmpooler__vm__#{vmname}", 'host', parent_host)
expect(subject).to receive(:run_select_hosts).with(poolname, {})
expect(subject).to receive(:vm_in_target?).and_return true
expect(subject).to receive(:migration_enabled?).and_return true
@@ -2622,11 +2621,13 @@ EOT
before(:each) do
subject.connection_pool.with_metrics do |pool_object|
expect(subject).to receive(:ensured_vsphere_connection).with(pool_object).and_return(connection)
- expect($redis).to receive(:scard).with('vmpooler__migration').and_return(5)
expect(subject).to receive(:get_vm_details).and_return(vm_details)
expect(subject).to_not receive(:run_select_hosts)
expect(subject).to_not receive(:vm_in_target?)
expect(subject).to receive(:migration_enabled?).and_return true
+ redis_connection_pool.with do |redis|
+ expect(redis).to receive(:scard).with('vmpooler__migration').and_return(5)
+ end
end
vm_object.summary.runtime.host = host_object
end
@@ -2691,14 +2692,10 @@ EOT
end
it' migrates a vm' do
- expect($redis).to receive(:sadd).with('vmpooler__migration', vmname)
expect(subject).to receive(:select_next_host).and_return(new_host)
- expect($redis).to receive(:hset).with("vmpooler__vm__#{vmname}", 'host', new_host)
- expect($redis).to receive(:hset).with("vmpooler__vm__#{vmname}", 'migrated', true)
expect(subject).to receive(:find_host_by_dnsname).and_return(new_host_object)
expect(subject).to receive(:migrate_vm_and_record_timing).and_return(format('%.2f', (Time.now - (Time.now - 15))))
expect(logger).to receive(:log).with('s', "[>] [#{poolname}] '#{vmname}' migrated from host1 to host2 in 15.00 seconds")
- expect($redis).to receive(:srem).with('vmpooler__migration', vmname)
subject.migrate_vm_to_new_host(poolname, vmname, vm_details, connection)
end
end
diff --git a/vmpooler.gemspec b/vmpooler.gemspec
index af239d0..b0c5ff2 100644
--- a/vmpooler.gemspec
+++ b/vmpooler.gemspec
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
s.add_dependency 'net-ldap', '~> 0.16'
s.add_dependency 'statsd-ruby', '~> 1.4'
s.add_dependency 'connection_pool', '~> 2.2'
+ s.add_dependency 'concurrent-ruby', '~> 1.1'
s.add_dependency 'nokogiri', '~> 1.10'
s.add_dependency 'spicy-proton', '~> 2.1'
end
diff --git a/vmpooler.yaml.example b/vmpooler.yaml.example
index c2d9c5b..d596724 100644
--- a/vmpooler.yaml.example
+++ b/vmpooler.yaml.example
@@ -195,14 +195,21 @@
#
# - data_ttl
# How long (in hours) to retain metadata in Redis after VM destruction.
-# (optional; default: '168')
+# (default: 168)
+#
+# - redis_connection_pool_size
+# Maximum number of connections to utilize for the redis connection pool.
+# (default: 10)
+#
+# - redis_connection_pool_timeout
+# How long a task should wait (in seconds) for a redis connection when all connections are in use.
+# (default: 5)
# Example:
:redis:
server: 'redis.example.com'
-
# :graphs:
#
# This section contains the server and prefix information for a graphite-
@@ -368,15 +375,19 @@
#
# - task_limit
# The number of concurrent VM creation tasks to perform.
-# (optional; default: '10')
+# (default: 10)
+#
+# - ondemand_clone_limit
+# The number of concurrent VM creation tasks to perform for ondemand VM requests.
+# (default: 10)
#
# - timeout
# How long (in minutes) before marking a clone in 'pending' queues as 'failed' and retrying.
-# (optional; default: '15')
+# (default: 15)
#
# - vm_checktime
# How often (in minutes) to check the sanity of VMs in 'ready' queues.
-# (optional; default: '1')
+# (default: 1)
#
# - vm_lifetime
# How long (in hours) to keep VMs in 'running' queues before destroying.
@@ -510,10 +521,21 @@
# Expects a string value
# (optional)
#
+# - max_ondemand_instances_per_request
+# The maximum number of instances any individual ondemand request may contain per pool.
+# (default: 10)
+#
+# - ondemand_request_ttl
+# The amount of time (in minutes) to give for a ondemand request to be fulfilled before considering it to have failed.
+# (default: 5)
+#
+# - ready_ttl
+# How long (in minutes) a ready VM should stay in the ready queue.
+# (default: 60)
+#
# Example:
-:config:
- site_name: 'vmpooler'
+:config: site_name: 'vmpooler'
logfile: '/var/log/vmpooler.log'
task_limit: 10
timeout: 15