# frozen_string_literal: true require 'vmpooler/providers' require 'spicy-proton' module Vmpooler class PoolManager CHECK_LOOP_DELAY_MIN_DEFAULT = 5 CHECK_LOOP_DELAY_MAX_DEFAULT = 60 CHECK_LOOP_DELAY_DECAY_DEFAULT = 2.0 def initialize(config, logger, redis, metrics) $config = config # Load logger library $logger = logger # metrics logging handle $metrics = metrics # Connect to Redis $redis = redis # VM Provider objects $providers = {} # Our thread-tracker object $threads = {} # Pool mutex @reconfigure_pool = {} @vm_mutex = {} # Name generator for generating host names @name_generator = Spicy::Proton.new # load specified providers from config file load_used_providers end def config $config end # 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] 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}") end end nil end # Check the state of a VM def check_pending_vm(vm, pool, timeout, provider) Thread.new do begin _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) raise end end end def _check_pending_vm(vm, pool, timeout, provider) mutex = vm_mutex(vm) 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) end end end def remove_nonexistent_vm(vm, pool) $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 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) $metrics.increment("errors.markedasfailed.#{pool}") $logger.log('d', "[!] [#{pool}] '#{vm}' marked as 'failed' after #{timeout} minutes") else remove_nonexistent_vm(vm, pool) end end true rescue StandardError => e $logger.log('d', "Fail pending VM failed with an error: #{e}") false end def move_pending_vm_to_ready(vm, pool) clone_time = $redis.hget('vmpooler__vm__' + vm, 'clone') finish = format('%