diff --git a/lib/vmpooler/dns.rb b/lib/vmpooler/dns.rb new file mode 100644 index 0000000..e11f229 --- /dev/null +++ b/lib/vmpooler/dns.rb @@ -0,0 +1,67 @@ +require 'pathname' + +module Vmpooler + class Dns + + # Load one or more VMPooler DNS plugin gems by name + # + # @param names [Array] The list of gem names to load + def self.load_by_name(names) + names = Array(names) + instance = new + names.map { |name| instance.load_from_gems(name) }.flatten + end + + # Returns the plugin class for the specified dns config by name + # + # @param config [Object] The entire VMPooler config object + # @param name [Symbol] The name of the dns config key to get the dns class + # @return [String] The plugin class for the specifid dns config + def self.get_dns_plugin_class_by_name(config, name) + dns_configs = config[:dns_configs].keys + plugin_class = '' + + dns_configs.map do |dns_config_name| + if dns_config_name.to_s == name + plugin_class = config[:dns_configs][dns_config_name]['dns_class'] + end + end + + plugin_class + end + + # Returns a list of DNS plugin classes specified in the vmpooler configuration + # + # @param config [Object] The entire VMPooler config object + # @return [Array e if request_id $logger.log('s', "[!] [#{pool_name}] failed while cloning VM for request #{request_id} with an error: #{e}") @@ -414,7 +421,7 @@ module Vmpooler [dns_ip, false] end - def _clone_vm(pool_name, provider, request_id = nil, pool_alias = nil) + def _clone_vm(pool_name, provider, dns_plugin, request_id = nil, pool_alias = nil) new_vmname = find_unique_hostname(pool_name) pool_domain = Parsing.get_domain_for_pool(config, pool_name) mutex = vm_mutex(new_vmname) @@ -447,6 +454,8 @@ module Vmpooler $logger.log('s', "[+] [#{pool_name}] '#{new_vmname}' cloned in #{finish} seconds") $metrics.timing("clone.#{pool_name}", finish) + + dns_plugin.create_or_replace_record(new_vmname) rescue StandardError @redis.with_metrics do |redis| redis.pipelined do |pipeline| @@ -632,6 +641,12 @@ module Vmpooler result end + # load only dns plugins used in config file + def load_used_dns_plugins + dns_plugins = Vmpooler::Dns.get_dns_plugin_config_classes(config) + Vmpooler::Dns.load_by_name(dns_plugins) + end + # load only providers used in config file def load_used_providers Vmpooler::Providers.load_by_name(used_providers) @@ -679,6 +694,15 @@ module Vmpooler $providers[provider_name] end + def get_dns_plugin_class_for_pool(pool_name) + pool = $config[:pools].find { |p| p['name'] == pool_name } + return nil unless pool + + plugin_name = pool.fetch('dns_plugin') + plugin_class = Vmpooler::Dns.get_dns_plugin_class_by_name(config, plugin_name) + $dns_plugins[plugin_class] + end + def check_disk_queue(maxloop = 0, loop_delay = 5) $logger.log('d', '[*] [disk_manager] starting worker thread') @@ -1290,6 +1314,8 @@ module Vmpooler $metrics.gauge("ready.#{pool_name}", ready) $metrics.gauge("running.#{pool_name}", running) + dns_plugin = get_dns_plugin_class_for_pool(pool_name) + unless pool_size == 0 if redis.get("vmpooler__empty__#{pool_name}") redis.del("vmpooler__empty__#{pool_name}") unless ready == 0 @@ -1304,7 +1330,7 @@ module Vmpooler begin redis.incr('vmpooler__tasks__clone') pool_check_response[:cloned_vms] += 1 - clone_vm(pool_name, provider) + clone_vm(pool_name, provider, dns_plugin) rescue StandardError => e $logger.log('s', "[!] [#{pool_name}] clone failed during check_pool with an error: #{e}") redis.decr('vmpooler__tasks__clone') @@ -1390,6 +1416,16 @@ module Vmpooler raise("Provider '#{provider_class}' is unknown for pool with provider name '#{provider_name}'") if provider_klass.nil? end + def create_dns_object(config, logger, metrics, redis_connection_pool, dns_class, dns_name, options) + dns_klass = Vmpooler::PoolManager::Dns + dns_klass.constants.each do |classname| + next unless classname.to_s.casecmp(dns_class) == 0 + + return dns_klass.const_get(classname).new(config, logger, metrics, redis_connection_pool, dns_name, options) + end + raise("DNS '#{dns_class}' is unknown for pool with dns name '#{dns_name}'") if dns_klass.nil? + end + def check_ondemand_requests(maxloop = 0, loop_delay_min = CHECK_LOOP_DELAY_MIN_DEFAULT, loop_delay_max = CHECK_LOOP_DELAY_MAX_DEFAULT, @@ -1473,20 +1509,21 @@ module Vmpooler pool_alias, pool, count, request_id = request.split(':') count = count.to_i provider = get_provider_for_pool(pool) + dns_plugin = get_dns_plugin_class_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) + clone_vm(pool, provider, dns_plugin, 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) + clone_vm(pool, provider, dns_plugin, request_id, pool_alias) end redis.pipelined do |pipeline| pipeline.zrem(queue_key, request) @@ -1601,6 +1638,7 @@ module Vmpooler # Create the providers $config[:pools].each do |pool| provider_name = pool['provider'] + dns_plugin_name = pool['dns_plugin'] # The provider_class parameter can be defined in the provider's data eg # :providers: # :vsphere: @@ -1621,12 +1659,22 @@ module Vmpooler else provider_class = $config[:providers][provider_name.to_sym]['provider_class'] end + begin $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 end + + dns_plugin_class = $config[:dns_configs][dns_plugin_name.to_sym]['dns_class'] + + begin + $dns_plugins[dns_plugin_class] = create_dns_object($config, $logger, $metrics, @redis, dns_plugin_class, dns_plugin_name, {}) if $dns_plugins[dns_plugin_class].nil? + rescue StandardError => e + $logger.log('s', "Error while creating dns plugin for pool #{pool['name']}: #{e}") + raise + end end purge_unused_vms_and_resources diff --git a/lib/vmpooler/providers/base.rb b/lib/vmpooler/providers/base.rb index 1147aa8..f02e088 100644 --- a/lib/vmpooler/providers/base.rb +++ b/lib/vmpooler/providers/base.rb @@ -34,6 +34,10 @@ module Vmpooler # Helper Methods + def get_dns_plugin_for_pool(pool_name) + + end + # inputs # [String] pool_name : Name of the pool to get the configuration # returns diff --git a/spec/unit/pool_manager_spec.rb b/spec/unit/pool_manager_spec.rb index df335fc..bfcead3 100644 --- a/spec/unit/pool_manager_spec.rb +++ b/spec/unit/pool_manager_spec.rb @@ -75,6 +75,27 @@ EOT end end + describe '#load_used_dns_plugins' do + let(:config) { YAML.load(<<-EOT +--- +:config: +:dns_configs: + :base: +:pools: + - name: '#{pool}' + size: 1 + provider: 'spoof' + EOT + ) + } + it do + files = ['vmpooler/dns/base'] + expect(subject.load_used_dns_plugins).to eq(files) + end + + + end + describe '#used_providers' do context 'with no named providers' do let(:config) { YAML.load(<<-EOT @@ -1724,6 +1745,51 @@ EOT end end + describe '#get_dns_plugin_class_name_for_pool' do + let(:config) { YAML.load(<<-EOT +--- +:dns_configs: + :mock: + dns_class: base +:pools: + - name: #{pool} + dns_plugin: 'mock' +EOT + + )} + before(:each) do + allow(Vmpooler::Dns).to receive(:load_used_dns_plugins).and_return('vmpooler/dns/mock') + end + + it 'calls Vmpooler::Dns.get_dns_plugin_class_by_name' do + expect(Vmpooler::Dns).to receive(:get_dns_plugin_class_by_name).with(config, 'mock') + + subject.get_dns_plugin_class_name_for_pool(pool) + end + end + + describe '#get_dns_plugin_domain_for_pool' do + let(:config) { YAML.load(<<-EOT +--- +:dns_configs: + :mock: + dns_class: base +:pools: + - name: #{pool} + dns_plugin: 'mock' +EOT + )} + before(:each) do + allow(Vmpooler::Dns).to receive(:load_used_dns_plugins).and_return('vmpooler/dns/mock') + end + + it 'calls Vmpooler::Dns.get_dns_plugin_domain_for_pool' do + expect(Vmpooler::Dns).to receive(:get_dns_plugin_domain_by_name).with(config, 'mock') + + subject.get_dns_plugin_domain_for_pool(pool) + end + end + describe '#check_disk_queue' do let(:threads) {[]} @@ -2817,6 +2883,46 @@ EOT subject.execute!(1,0) end + context 'creating Dns plugins' do + let(:mock_dns_plugin) { double('mock_dns_plugin') } + let(:config) { + YAML.load(<<-EOT +--- +:dns_configs: + :mock: + dns_class: base +:pools: + - name: #{pool} + dns_plugin: 'mock' + - name: 'dummy' + dns_plugin: 'mock' + - name: 'dummy2' + dns_plugin: 'mock' +EOT + )} + + it 'should call create_dns_object idempotently' do + # Even though there are two pools using the mock dns plugin, it should only + # create the dns object once. + expect(subject).to receive(:create_dns_object).and_return(mock_dns_plugin) + + subject.execute!(1,0) + end + + it 'should raise an error if the dns plugin cannot be created' do + expect(subject).to receive(:create_dns_object).and_raise(RuntimeError, "MockError") + + expect{ subject.execute!(1,0) }.to raise_error(/MockError/) + end + + it 'should log a message if the dns plugin cannot be created' do + expect(subject).to receive(:create_dns_object).and_raise(RuntimeError, "MockError") + expect(logger).to receive(:log).with('s',"Error while creating dns plugin for pool #{pool}: MockError") + + expect{ subject.execute!(1,0) }.to raise_error(/MockError/) + end + end + context 'creating Providers' do let(:vsphere_provider) { double('vsphere_provider') } let(:config) {