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/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..a42fe09 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,6 +9,9 @@ services: - type: bind source: ${PWD}/vmpooler.yaml target: /etc/vmpooler/vmpooler.yaml + - type: bind + source: ${PWD}/providers.yaml + target: /etc/vmpooler/providers.yaml ports: - "4567:4567" networks: @@ -18,6 +21,7 @@ services: - VMPOOLER_CONFIG_FILE=/etc/vmpooler/vmpooler.yaml - REDIS_SERVER=redislocal - LOGFILE=/dev/stdout + - EXTRA_CONFIG=/etc/vmpooler/providers.yaml image: vmpooler-local depends_on: - redislocal diff --git a/lib/vmpooler.rb b/lib/vmpooler.rb index 5ae00f1..ecf68f5 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' diff --git a/lib/vmpooler/api/v1.rb b/lib/vmpooler/api/v1.rb index 9b1cd66..92e9045 100644 --- a/lib/vmpooler/api/v1.rb +++ b/lib/vmpooler/api/v1.rb @@ -42,26 +42,73 @@ module Vmpooler Vmpooler::API.settings.checkoutlock end - def fetch_single_vm(template) - template_backends = [template] + def get_template_aliases(template) + result = [] aliases = Vmpooler::API.settings.config[:alias] if aliases - template_backends += aliases[template] if aliases[template].is_a?(Array) + result += aliases[template] if aliases[template].is_a?(Array) template_backends << aliases[template] if aliases[template].is_a?(String) - pool_index = pool_index(pools) - weighted_pools = {} - template_backends.each do |t| - next unless pool_index.key? t + end + result + end - index = pool_index[t] - clone_target = pools[index]['clone_target'] || config['clone_target'] - next unless config.key?('backend_weight') + def get_pool_weights(template_backends) + pool_index = pool_index(pools) + weighted_pools = {} + template_backends.each do |t| + next unless pool_index.key? t - weight = config['backend_weight'][clone_target] - if weight - weighted_pools[t] = weight + 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] + 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.times do + if pickup + selection << pickup.pick + else + selection << template_backends.sample end end + else + count.times do + selection << template + end + end + + return count_selection(selection) + end + + def fetch_single_vm(template) + template_backends = [template] + aliases = get_template_aliases(template) + if aliases + template_backends += aliases + weighted_pools = get_pool_weights(template_backends) if weighted_pools.count == template_backends.count pickup = Pickup.new(weighted_pools) @@ -295,8 +342,15 @@ module Vmpooler status 201 - requested = payload[:requested].map { |poolname, count| "#{poolname}:#{count}" }.join(',') - backend.hset("vmpooler__odrequest__#{request_id}", 'requested', requested) + requested = payload[:requested] + platforms_with_aliases = [] + requested.each do |poolname, count| + selection = evaluate_template_aliases(poolname, count) + selection.map { |aliasname, count| platforms_with_aliases << "#{poolname}:#{aliasname}:#{count}" } + end + + platforms_string = platforms_with_aliases.join(',') + backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string) result['ok'] = true result diff --git a/lib/vmpooler/pool_manager.rb b/lib/vmpooler/pool_manager.rb index eaad5c0..ebd4dc3 100644 --- a/lib/vmpooler/pool_manager.rb +++ b/lib/vmpooler/pool_manager.rb @@ -35,6 +35,8 @@ module Vmpooler # Name generator for generating host names @name_generator = Spicy::Proton.new + @tasks = Concurrent::Hash.new + # load specified providers from config file load_used_providers end @@ -86,10 +88,11 @@ module Vmpooler return if mutex.locked? mutex.synchronize do + request_id = $redis.hget("vmpooler__vm__#{vm}", request_id) if provider.vm_ready?(pool, vm) - move_pending_vm_to_ready(vm, pool) + move_pending_vm_to_ready(vm, pool, request_id) else - fail_pending_vm(vm, pool, timeout) + fail_pending_vm(vm, pool, timeout, request_id=request_id) end end end @@ -99,14 +102,17 @@ module Vmpooler $logger.log('d', "[!] [#{pool}] '#{vm}' no longer exists. Removing from pending.") end - def fail_pending_vm(vm, pool, timeout, exists = true) + def fail_pending_vm(vm, pool, timeout, exists = true, request_id = nil) clone_stamp = $redis.hget("vmpooler__vm__#{vm}", 'clone') return true unless clone_stamp time_since_clone = (Time.now - Time.parse(clone_stamp)) / 60 if time_since_clone > timeout if exists + $redis.multi $redis.smove('vmpooler__pending__' + pool, 'vmpooler__completed__' + pool, vm) + $redis.zadd('vmpooler__odcreate__task', 1, "#{pool_name}: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 @@ -119,16 +125,28 @@ module Vmpooler false end - def move_pending_vm_to_ready(vm, pool) + def move_pending_vm_to_ready(vm, pool, request_id) clone_time = $redis.hget('vmpooler__vm__' + vm, 'clone') finish = format('%