From 456da4503916e1e5f28756e8cfe9046ccbdbac86 Mon Sep 17 00:00:00 2001 From: Dmitri Dolguikh Date: Wed, 24 Jun 2015 12:44:17 +0100 Subject: [PATCH] Fixes #11081: broke out dhcp providers into separate modules --- config/settings.d/dhcp.yml.example | 29 +-- config/settings.d/dhcp_isc.yml.example | 24 +++ config/settings.d/dhcp_ms_native.yml.example | 6 + config/settings.d/dhcp_virsh.yml.example | 8 + .../20150826000000_migrate_dhcp_settings.rb | 69 +++++++ lib/proxy/provider.rb | 2 +- lib/proxy/provider_factory.rb | 5 +- lib/proxy/settings/global.rb | 3 +- lib/smart_proxy_main.rb | 3 + modules/dhcp/dhcp.rb | 27 --- modules/dhcp/dhcp_api.rb | 45 ++--- modules/dhcp/dhcp_plugin.rb | 8 +- modules/dhcp/subnet_service.rb | 113 ----------- .../dependency_injection/container.rb | 21 ++ .../dependency_injection/dependencies.rb | 10 + modules/dhcp_common/dhcp_common.rb | 27 +++ .../monkey_patch_subnet.rb | 0 .../{dhcp => dhcp_common}/monkey_patches.rb | 0 modules/{dhcp => dhcp_common}/record.rb | 2 +- .../record/deleted_reservation.rb | 2 +- modules/{dhcp => dhcp_common}/record/lease.rb | 2 +- .../record/reservation.rb | 2 +- modules/{dhcp => dhcp_common}/server.rb | 52 ++--- modules/{dhcp => dhcp_common}/subnet.rb | 4 +- modules/dhcp_common/subnet_service.rb | 105 ++++++++++ modules/dhcp_isc/dependencies.rb | 5 + modules/dhcp_isc/dhcp_isc.rb | 2 + .../isc.rb => dhcp_isc/dhcp_isc_main.rb} | 182 ++++++++++-------- modules/dhcp_isc/dhcp_isc_plugin.rb | 17 ++ modules/dhcp_native_ms/dependencies.rb | 5 + modules/dhcp_native_ms/dhcp_native_ms.rb | 2 + .../dhcp_native_ms_main.rb} | 72 ++++--- .../dhcp_native_ms/dhcp_native_ms_plugin.rb | 12 ++ modules/dhcp_virsh/dependencies.rb | 5 + modules/dhcp_virsh/dhcp_virsh.rb | 2 + .../dhcp_virsh_main.rb} | 38 ++-- modules/dhcp_virsh/dhcp_virsh_plugin.rb | 12 ++ test/dhcp/dhcp_api_test.rb | 18 +- test/dhcp/dhcp_config_test.rb | 5 +- test/dhcp/record_test.rb | 7 +- test/dhcp/server_test.rb | 32 +-- test/dhcp/subnet_service_test.rb | 25 +-- test/dhcp/subnet_test.rb | 6 +- test/dhcp_isc/dhcp_isc_config_test.rb | 11 ++ .../dhcp_isc_provider_interface_test.rb | 8 + test/{dhcp => dhcp_isc}/server_isc_test.rb | 75 +++++--- .../dhcp_ms_native_provider_interface_test.rb | 8 + .../server_ms_test.rb | 31 +-- .../dhcp_virsh_provider_interface_test.rb | 8 + test/dhcp_virsh/virsh_provider_test.rb | 79 ++++++++ test/migrations/dhcp_migration_test.rb | 71 +++++++ .../dhcp_provider.rb | 14 ++ test/test_helper.rb | 3 + 53 files changed, 867 insertions(+), 457 deletions(-) create mode 100644 config/settings.d/dhcp_isc.yml.example create mode 100644 config/settings.d/dhcp_ms_native.yml.example create mode 100644 config/settings.d/dhcp_virsh.yml.example create mode 100644 extra/migrations/20150826000000_migrate_dhcp_settings.rb delete mode 100644 modules/dhcp/subnet_service.rb create mode 100644 modules/dhcp_common/dependency_injection/container.rb create mode 100644 modules/dhcp_common/dependency_injection/dependencies.rb create mode 100644 modules/dhcp_common/dhcp_common.rb rename modules/{dhcp => dhcp_common}/monkey_patch_subnet.rb (100%) rename modules/{dhcp => dhcp_common}/monkey_patches.rb (100%) rename modules/{dhcp => dhcp_common}/record.rb (97%) rename modules/{dhcp => dhcp_common}/record/deleted_reservation.rb (87%) rename modules/{dhcp => dhcp_common}/record/lease.rb (93%) rename modules/{dhcp => dhcp_common}/record/reservation.rb (94%) rename modules/{dhcp => dhcp_common}/server.rb (74%) rename modules/{dhcp => dhcp_common}/subnet.rb (97%) create mode 100644 modules/dhcp_common/subnet_service.rb create mode 100644 modules/dhcp_isc/dependencies.rb create mode 100644 modules/dhcp_isc/dhcp_isc.rb rename modules/{dhcp/providers/server/isc.rb => dhcp_isc/dhcp_isc_main.rb} (75%) create mode 100644 modules/dhcp_isc/dhcp_isc_plugin.rb create mode 100644 modules/dhcp_native_ms/dependencies.rb create mode 100644 modules/dhcp_native_ms/dhcp_native_ms.rb rename modules/{dhcp/providers/server/native_ms.rb => dhcp_native_ms/dhcp_native_ms_main.rb} (85%) create mode 100644 modules/dhcp_native_ms/dhcp_native_ms_plugin.rb create mode 100644 modules/dhcp_virsh/dependencies.rb create mode 100644 modules/dhcp_virsh/dhcp_virsh.rb rename modules/{dhcp/providers/server/virsh.rb => dhcp_virsh/dhcp_virsh_main.rb} (73%) create mode 100644 modules/dhcp_virsh/dhcp_virsh_plugin.rb create mode 100644 test/dhcp_isc/dhcp_isc_config_test.rb create mode 100644 test/dhcp_isc/dhcp_isc_provider_interface_test.rb rename test/{dhcp => dhcp_isc}/server_isc_test.rb (75%) create mode 100644 test/dhcp_ms_native/dhcp_ms_native_provider_interface_test.rb rename test/{dhcp => dhcp_ms_native}/server_ms_test.rb (88%) create mode 100644 test/dhcp_virsh/dhcp_virsh_provider_interface_test.rb create mode 100644 test/dhcp_virsh/virsh_provider_test.rb create mode 100644 test/migrations/dhcp_migration_test.rb create mode 100644 test/provider_interface_validation/dhcp_provider.rb diff --git a/config/settings.d/dhcp.yml.example b/config/settings.d/dhcp.yml.example index a7eed58bd..28c97aa9e 100644 --- a/config/settings.d/dhcp.yml.example +++ b/config/settings.d/dhcp.yml.example @@ -2,24 +2,11 @@ # Can be true, false, or http/https to enable just one of the protocols :enabled: false -# valid vendors: -# - isc -# - native_ms (Microsoft native implementation) -# - virsh (simple implementation for libvirt) -#:dhcp_vendor: isc -#:dhcp_server: 127.0.0.1 -#:dhcp_omapi_port: 7911 -# dhcp_subnets is an ISC & Native MS implementation setting. It restricts the subnets queried to a -# subset, so as to reduce the query time. -#:dhcp_subnets: [192.168.205.0/255.255.255.128, 192.168.205.128/255.255.255.128] -# Settings for Ubuntu ISC -#:dhcp_config: /etc/dhcp3/dhcpd.conf -#:dhcp_leases: /var/lib/dhcp3/dhcpd.leases -# Settings for Redhat ISC -# Redhat 5 -#:dhcp_config: /etc/dhcpd.conf -# Redhat 6 -#:dhcp_config: /etc/dhcp/dhcpd.conf -#:dhcp_leases: /var/lib/dhcpd/dhcpd.leases -#:dhcp_key_name: secret_key_name -#:dhcp_key_secret: secret_key +# valid providers: +# - dhcp_isc (ISC dhcp server) +# - dhcp_native_ms (Microsoft native implementation) +# - dhcp_virsh (simple implementation for libvirt) +#:use_provider: dhcp_isc +#:server: 127.0.0.1 +# subnets restricts the subnets queried to a subset, to reduce the query time. +#:subnets: [192.168.205.0/255.255.255.128, 192.168.205.128/255.255.255.128] diff --git a/config/settings.d/dhcp_isc.yml.example b/config/settings.d/dhcp_isc.yml.example new file mode 100644 index 000000000..2434dcec8 --- /dev/null +++ b/config/settings.d/dhcp_isc.yml.example @@ -0,0 +1,24 @@ +--- +# +# Configuration file for ISC dhcp provider +# + +#:config: /etc/dhcp/dhcpd.conf +#:leases: /var/lib/dhcpd/dhcpd.leases +# +# Redhat 5 +# +#:config: /etc/dhcpd.conf +# +# Settings for Ubuntu +# +#:config: /etc/dhcp3/dhcpd.conf +#:leases: /var/lib/dhcp3/dhcpd.leases + +# Specifies TSIG key name and secret +#:key_name: secret_key_name +#:key_secret: secret_key + +#:omapi_port: 7911 + +# use :server setting in dhcp.yml if you are managing a dhcp server which is not localhost diff --git a/config/settings.d/dhcp_ms_native.yml.example b/config/settings.d/dhcp_ms_native.yml.example new file mode 100644 index 000000000..9019ea179 --- /dev/null +++ b/config/settings.d/dhcp_ms_native.yml.example @@ -0,0 +1,6 @@ +--- +# +# Configuration file for Windows-specific 'netsh' dhcp provider +# +# There's currently no configuration options for this provider. +# use :server setting in dhcp.yml if you are managing a dhcp server which is not localhost diff --git a/config/settings.d/dhcp_virsh.yml.example b/config/settings.d/dhcp_virsh.yml.example new file mode 100644 index 000000000..55b673b30 --- /dev/null +++ b/config/settings.d/dhcp_virsh.yml.example @@ -0,0 +1,8 @@ +--- +# +# Configuration file for libvirtd-specific 'virsh' dhcp provider +# +# There's currently no configuration options for this provider. +# Virsh network name is a global parameter that can be set +# in the main settings.yml file in 'virsh_network' parameter. +# diff --git a/extra/migrations/20150826000000_migrate_dhcp_settings.rb b/extra/migrations/20150826000000_migrate_dhcp_settings.rb new file mode 100644 index 000000000..4a8148e8f --- /dev/null +++ b/extra/migrations/20150826000000_migrate_dhcp_settings.rb @@ -0,0 +1,69 @@ +require 'yaml' + +class MigrateDhcpSettings < ::Proxy::Migration + KNOWN_PARAMETERS = { + :enabled => [:dhcp, :enabled], + :dhcp_vendor => [:dhcp, :use_provider, :old_provider_name_to_new], + :dhcp_subnets => [:dhcp, :subnets], + :dhcp_config => [:dhcp_isc, :config], + :dhcp_leases => [:dhcp_isc, :leases], + :dhcp_key_name => [:dhcp_isc, :key_name], + :dhcp_key_secret => [:dhcp_isc, :key_secret], + :dhcp_omapi_port => [:dhcp_isc, :omapi_port], + :dhcp_server => [:dhcp, :server] + } + + def remap_parameter(aparameter, avalue) + module_name, parameter_name, converter = + KNOWN_PARAMETERS.has_key?(aparameter) ? KNOWN_PARAMETERS[aparameter] : [:unknown, aparameter] + + converter.nil? ? [module_name, parameter_name, avalue] : [module_name, parameter_name, send(converter, avalue)] + end + + def migrate + dhcp_config = path(src_dir, "settings.d", "dhcp.yml") + if !File.exist?(dhcp_config) + duplicate_original_configuration + return + end + + to_migrate = YAML.load_file(dhcp_config) + output = migrate_dhcp_configuration(to_migrate) + copy_original_configuration_except(path("settings.d", "dhcp.yml")) + write_to_files(output) + end + + def old_provider_name_to_new(aname) + if ['isc', 'native_ms', 'virsh'].include?(aname) + 'dhcp_' + aname + else + aname + end + end + + def migrate_dhcp_configuration(to_migrate) + migrated = Hash.new { |h,k| h[k] = Hash.new } + to_migrate.each do |option, value| + module_name, parameter_name, parameter_value = remap_parameter(option, value) + migrated[module_name][parameter_name] = parameter_value + end + migrated + end + + def write_to_files(output) + output.keys.each do |m| + next if output[m].empty? || m == :unknown + File.open(path(dst_dir, "settings.d", "#{m}.yml"),'w') do |f| + f.write(strip_ruby_symbol_encoding(output[m].to_yaml)) + if (m == :dhcp) && !output[:unknown].empty? + f.write "\n# Unparsed options, please review\n" + f.write(strip_ruby_symbol_encoding(output[:unknown].to_yaml).gsub(/^---/,'')) + end + end + end + end + + def strip_ruby_symbol_encoding(astring) + astring.gsub("!ruby/symbol ", ":").gsub("!ruby/sym ", ":") + end +end diff --git a/lib/proxy/provider.rb b/lib/proxy/provider.rb index f9067e34b..227539c40 100644 --- a/lib/proxy/provider.rb +++ b/lib/proxy/provider.rb @@ -3,7 +3,7 @@ class ::Proxy::Provider include ::Proxy::Log class << self - attr_reader :provider_factory_proc + attr_reader :provider_factory def plugin(plugin_name, aversion, attrs = {}) @plugin_name = plugin_name.to_sym diff --git a/lib/proxy/provider_factory.rb b/lib/proxy/provider_factory.rb index ac78d5072..f58609b19 100644 --- a/lib/proxy/provider_factory.rb +++ b/lib/proxy/provider_factory.rb @@ -1,8 +1,9 @@ class ::Proxy::ProviderFactory class << self - def get_provider(provider_name, opts) + def get_provider(provider_name, opts = {}) provider = ::Proxy::Plugins.find_provider(provider_name.to_sym) - provider.provider_factory.call(opts) + pf = provider.provider_factory + pf.is_a?(Proc) ? pf.call(opts) : pf.new.get_provider(opts) end end end diff --git a/lib/proxy/settings/global.rb b/lib/proxy/settings/global.rb index 03bbdacad..2de01a2c9 100644 --- a/lib/proxy/settings/global.rb +++ b/lib/proxy/settings/global.rb @@ -8,7 +8,8 @@ class Global < ::OpenStruct :daemon => false, :daemon_pid => "/var/run/foreman-proxy/foreman-proxy.pid", :forward_verify => true, - :bind_host => "*" + :bind_host => "*", + :virsh_network => 'default' } HOW_TO_NORMALIZE = { diff --git a/lib/smart_proxy_main.rb b/lib/smart_proxy_main.rb index 369768d5c..09f04a2a2 100644 --- a/lib/smart_proxy_main.rb +++ b/lib/smart_proxy_main.rb @@ -62,6 +62,9 @@ module Proxy require 'templates/templates' require 'tftp/tftp' require 'dhcp/dhcp' + require 'dhcp_isc/dhcp_isc' + require 'dhcp_native_ms/dhcp_native_ms' + require 'dhcp_virsh/dhcp_virsh' require 'puppetca/puppetca' require 'puppet_proxy/puppet' require 'bmc/bmc' diff --git a/modules/dhcp/dhcp.rb b/modules/dhcp/dhcp.rb index d80c5a723..042d74e31 100644 --- a/modules/dhcp/dhcp.rb +++ b/modules/dhcp/dhcp.rb @@ -1,28 +1 @@ require 'dhcp/dhcp_plugin' - -module Proxy::DHCP - Standard = { - :hostname => {:code => 12, :kind => "String" }, # The host's name - :PXEClient => {:code => 60, :kind => "String" }, # Needs to be empty for foreman to function - :nextServer => {:code => 66, :kind => "String" }, # From where we download the pxeboot image via TFTP - :filename => {:code => 67, :kind => "String" } # The pxeboot image - } - SUNW = { - :root_server_ip => {:code => 2, :kind => "IPAddress" }, # 192.168.216.241 - :root_server_hostname => {:code => 3, :kind => "String" }, # mediahost - :root_path_name => {:code => 4, :kind => "String" }, # /vol/solgi_5.10/sol10_hw0910/Solaris_10/Tools/Boot - :install_server_ip => {:code => 10, :kind => "IPAddress" }, # 192.168.216.241 - :install_server_name => {:code => 11, :kind => "String" }, # mediahost - :install_path => {:code => 12, :kind => "String" }, # /vol/solgi_5.10/sol10_hw0910 - :sysid_server_path => {:code => 13, :kind => "String" }, # 192.168.216.241:/vol/jumpstart/sysidcfg/sysidcfg_primary - :jumpstart_server_path => {:code => 14, :kind => "String" } # 192.168.216.241:/vol/jumpstart - } - class Error < RuntimeError; end - class Collision < RuntimeError; end - class InvalidRecord < RuntimeError; end - class AlreadyExists < RuntimeError; end - - def kind - self.class.to_s.sub("Proxy::DHCP::","").downcase - end -end diff --git a/modules/dhcp/dhcp_api.rb b/modules/dhcp/dhcp_api.rb index bcc910236..53be7b77f 100644 --- a/modules/dhcp/dhcp_api.rb +++ b/modules/dhcp/dhcp_api.rb @@ -1,31 +1,16 @@ class Proxy::DhcpApi < ::Sinatra::Base + extend Proxy::DHCP::DependencyInjection::Injectors + helpers ::Proxy::Helpers authorize_with_trusted_hosts authorize_with_ssl_client use Rack::MethodOverride + inject_attr :dhcp_provider, :server + before do begin - raise "Smart Proxy is not configured to support DHCP" unless Proxy::DhcpPlugin.settings.enabled - case Proxy::DhcpPlugin.settings.dhcp_vendor.downcase - when "isc" - require 'dhcp/providers/server/isc' - unless Proxy::DhcpPlugin.settings.dhcp_config && Proxy::DhcpPlugin.settings.dhcp_leases \ - && File.exist?(Proxy::DhcpPlugin.settings.dhcp_config) && File.exist?(Proxy::DhcpPlugin.settings.dhcp_leases) - log_halt 400, "Unable to find the DHCP configuration or lease files" - end - @server = Proxy::DHCP::ISC.instance_with_default_parameters - when "native_ms" - require 'dhcp/providers/server/native_ms' - @server = Proxy::DHCP::NativeMS.instance_with_default_parameters - when "virsh" - require 'dhcp/providers/server/virsh' - @server = Proxy::DHCP::Virsh.instance_with_default_parameters - else - log_halt 400, "Unrecognized or missing DHCP vendor type: #{Proxy::DhcpPlugin.settings.dhcp_vendor.nil? ? 'MISSING' : Proxy::DhcpPlugin.settings.dhcp_vendor}" - end - - @server.loadSubnets + server.load_subnets rescue => e log_halt 400, e end @@ -33,13 +18,13 @@ class Proxy::DhcpApi < ::Sinatra::Base helpers do def load_subnet - @subnet = @server.find_subnet(params[:network]) + @subnet = server.find_subnet(params[:network]) log_halt 404, "Subnet #{params[:network]} not found" unless @subnet @subnet end def load_subnet_data - @server.loadSubnetData(@subnet) + server.load_subnet_data(@subnet) end end @@ -48,8 +33,8 @@ def load_subnet_data if request.accept? 'application/json' content_type :json - log_halt 404, "No subnets found on server @{name}" unless @server.subnets - @server.subnets.map{|s| {:network => s.network, :netmask => s.netmask, :options => s.options}}.to_json + log_halt 404, "No subnets found on server @{name}" unless server.subnets + server.subnets.map{|s| {:network => s.network, :netmask => s.netmask, :options => s.options}}.to_json else erb :"dhcp/index" end @@ -65,7 +50,7 @@ def load_subnet_data if request.accept? 'application/json' content_type :json - {:reservations => @server.all_hosts(@subnet.network), :leases => @server.all_leases(@subnet.network)}.to_json + {:reservations => server.all_hosts(@subnet.network), :leases => server.all_leases(@subnet.network)}.to_json else erb :"dhcp/show" end @@ -81,7 +66,7 @@ def load_subnet_data load_subnet load_subnet_data - ({:ip => @server.unused_ip(@subnet, params[:mac], params[:from], params[:to])}).to_json + ({:ip => server.unused_ip(@subnet, params[:mac], params[:from], params[:to])}).to_json rescue => e log_halt 400, e end @@ -94,7 +79,7 @@ def load_subnet_data load_subnet load_subnet_data - record = @server.find_record(@subnet.network, params[:record]) + record = server.find_record(@subnet.network, params[:record]) log_halt 404, "Record #{params[:network]}/#{params[:record]} not found" unless record record.options.to_json rescue => e @@ -109,7 +94,7 @@ def load_subnet_data load_subnet_data content_type :json - @server.addRecord(params) + server.add_record(params) rescue Proxy::DHCP::Collision => e log_halt 409, e rescue Proxy::DHCP::AlreadyExists # rubocop:disable Lint/HandleExceptions @@ -125,9 +110,9 @@ def load_subnet_data load_subnet load_subnet_data - record = @server.find_record(@subnet.network, params[:record]) + record = server.find_record(@subnet.network, params[:record]) log_halt 404, "Record #{params[:network]}/#{params[:record]} not found" unless record - @server.delRecord @subnet, record + server.del_record @subnet, record if request.accept? 'application/json' content_type :json {} diff --git a/modules/dhcp/dhcp_plugin.rb b/modules/dhcp/dhcp_plugin.rb index 7da8ee1a0..5652c192f 100644 --- a/modules/dhcp/dhcp_plugin.rb +++ b/modules/dhcp/dhcp_plugin.rb @@ -2,6 +2,12 @@ class Proxy::DhcpPlugin < ::Proxy::Plugin http_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__)) https_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__)) - default_settings :dhcp_provider => 'isc', :dhcp_server => '127.0.0.1', :dhcp_omapi_port => '7911' + uses_provider + default_settings :use_provider => 'dhcp_isc', :server => '127.0.0.1' plugin :dhcp, ::Proxy::VERSION + + after_activation do + require 'dhcp_common/dependency_injection/container' + require 'dhcp/dhcp_api' + end end diff --git a/modules/dhcp/subnet_service.rb b/modules/dhcp/subnet_service.rb deleted file mode 100644 index 4bdf0f539..000000000 --- a/modules/dhcp/subnet_service.rb +++ /dev/null @@ -1,113 +0,0 @@ -class Proxy::DHCP::SubnetService - include Proxy::Log - - def self.instance_with_default_parameters - Proxy::DHCP::SubnetService.new(Proxy::MemoryStore.new, Proxy::MemoryStore.new, - Proxy::MemoryStore.new, Proxy::MemoryStore.new, - Proxy::MemoryStore.new, Proxy::MemoryStore.new) - end - - # rubocop:disable Metrics/ParameterLists - def initialize(subnets_store, leases_ip_store, leases_mac_store, reservations_ip_store, reservations_mac_store, - reservations_name_store) - @subnets = subnets_store - @leases_by_ip = leases_ip_store - @leases_by_mac = leases_mac_store - @reservations_by_ip = reservations_ip_store - @reservations_by_mac = reservations_mac_store - @reservations_by_name = reservations_name_store - end - # rubocop:enable Metrics/ParameterLists - - def add_subnet(subnet) - raise Proxy::DHCP::Error, "Unable to add subnet #{subnet}" if find_subnet(subnet.network) - logger.debug("Added a subnet: #{subnet.network}") - @subnets[subnet.network] = subnet - end - - def add_subnets(*subnets) - subnets.each { |s| add_subnet(s) } - subnets - end - - def delete_subnet(subnet_address) - @subnets.delete(subnet_address) - logger.debug("Deleted a subnet: #{subnet_address}") - end - - def find_subnet(address) - to_ret = @subnets[address] - return to_ret if to_ret # we were given a subnet address - - # TODO: this can be done much faster - @subnets.values.each do |subnet| - return subnet if subnet.include?(address) - end - - nil - end - - def all_subnets - @subnets.values - end - - def add_lease(subnet_address, record) - @leases_by_ip[subnet_address, record.ip] = record - @leases_by_mac[subnet_address, record.mac] = record - logger.debug("Added a lease record: #{record.ip}:#{record.mac}") - end - - def add_host(subnet_address, record) - @reservations_by_ip[subnet_address, record.ip] = record - @reservations_by_mac[subnet_address, record.mac] = record - @reservations_by_name[record.name] = record - logger.debug("Added a reservation: #{record.ip}:#{record.mac}:#{record.name}") - end - - def delete_lease(record) - @leases_by_ip.delete(record.subnet.network, record.ip) - @leases_by_mac.delete(record.subnet.network, record.mac) - logger.debug("Deleted a lease record: #{record.ip}:#{record.mac}") - end - - def delete_host(record) - @reservations_by_ip.delete(record.subnet.network, record.ip) - @reservations_by_mac.delete(record.subnet.network, record.mac) - @reservations_by_name.delete(record.name) - logger.debug("Deleted a reservation: #{record.ip}:#{record.mac}:#{record.name}") - end - - def find_lease_by_mac(subnet_address, mac_address) - @leases_by_mac[subnet_address, mac_address] - end - - def find_host_by_mac(subnet_address, mac_address) - @reservations_by_mac[subnet_address, mac_address] - end - - def find_lease_by_ip(subnet_address, ip_address) - @leases_by_ip[subnet_address, ip_address] - end - - def find_host_by_ip(subnet_address, ip_address) - @reservations_by_ip[subnet_address, ip_address] - end - - def find_host_by_hostname(hostname) - return @reservations_by_name[hostname] - end - - def all_hosts(subnet_address = nil) - if subnet_address - return @reservations_by_ip[subnet_address] ? @reservations_by_ip.values(subnet_address) : [] - end - @reservations_by_ip.values - end - - def all_leases(subnet_address = nil) - if subnet_address - return @leases_by_ip[subnet_address] ? @leases_by_ip.values(subnet_address) : [] - end - @leases_by_ip.values - end -end diff --git a/modules/dhcp_common/dependency_injection/container.rb b/modules/dhcp_common/dependency_injection/container.rb new file mode 100644 index 000000000..455217feb --- /dev/null +++ b/modules/dhcp_common/dependency_injection/container.rb @@ -0,0 +1,21 @@ +module Proxy::DHCP + module DependencyInjection + class Container < Proxy::DependencyInjection::Container; end + + module Wiring + include Proxy::DependencyInjection::Wiring + + def container_instance + Container.instance + end + end + + module Injectors + include Proxy::DependencyInjection::Accessors + + def container_instance + Container.instance + end + end + end +end diff --git a/modules/dhcp_common/dependency_injection/dependencies.rb b/modules/dhcp_common/dependency_injection/dependencies.rb new file mode 100644 index 000000000..6162d98c9 --- /dev/null +++ b/modules/dhcp_common/dependency_injection/dependencies.rb @@ -0,0 +1,10 @@ +module Proxy::DHCP + module DependencyInjection + class Dependencies + extend Wiring + + dependency :memory_store, ::Proxy::MemoryStore + dependency :subnet_service, ::Proxy::DHCP::SubnetService + end + end +end diff --git a/modules/dhcp_common/dhcp_common.rb b/modules/dhcp_common/dhcp_common.rb new file mode 100644 index 000000000..94f3f4327 --- /dev/null +++ b/modules/dhcp_common/dhcp_common.rb @@ -0,0 +1,27 @@ +module Proxy::DHCP + Standard = { + :hostname => {:code => 12, :kind => "String" }, # The host's name + :PXEClient => {:code => 60, :kind => "String" }, # Needs to be empty for foreman to function + :nextServer => {:code => 66, :kind => "String" }, # From where we download the pxeboot image via TFTP + :filename => {:code => 67, :kind => "String" } # The pxeboot image + } + SUNW = { + :root_server_ip => {:code => 2, :kind => "IPAddress" }, # 192.168.216.241 + :root_server_hostname => {:code => 3, :kind => "String" }, # mediahost + :root_path_name => {:code => 4, :kind => "String" }, # /vol/solgi_5.10/sol10_hw0910/Solaris_10/Tools/Boot + :install_server_ip => {:code => 10, :kind => "IPAddress" }, # 192.168.216.241 + :install_server_name => {:code => 11, :kind => "String" }, # mediahost + :install_path => {:code => 12, :kind => "String" }, # /vol/solgi_5.10/sol10_hw0910 + :sysid_server_path => {:code => 13, :kind => "String" }, # 192.168.216.241:/vol/jumpstart/sysidcfg/sysidcfg_primary + :jumpstart_server_path => {:code => 14, :kind => "String" } # 192.168.216.241:/vol/jumpstart + } + + class Error < RuntimeError; end + class Collision < RuntimeError; end + class InvalidRecord < RuntimeError; end + class AlreadyExists < RuntimeError; end + + def kind + self.class.to_s.sub("Proxy::DHCP::","").downcase + end +end diff --git a/modules/dhcp/monkey_patch_subnet.rb b/modules/dhcp_common/monkey_patch_subnet.rb similarity index 100% rename from modules/dhcp/monkey_patch_subnet.rb rename to modules/dhcp_common/monkey_patch_subnet.rb diff --git a/modules/dhcp/monkey_patches.rb b/modules/dhcp_common/monkey_patches.rb similarity index 100% rename from modules/dhcp/monkey_patches.rb rename to modules/dhcp_common/monkey_patches.rb diff --git a/modules/dhcp/record.rb b/modules/dhcp_common/record.rb similarity index 97% rename from modules/dhcp/record.rb rename to modules/dhcp_common/record.rb index dccc210cf..010989156 100644 --- a/modules/dhcp/record.rb +++ b/modules/dhcp_common/record.rb @@ -1,4 +1,4 @@ -require "dhcp/subnet" +require "dhcp_common/subnet" require "proxy/validations" module Proxy::DHCP diff --git a/modules/dhcp/record/deleted_reservation.rb b/modules/dhcp_common/record/deleted_reservation.rb similarity index 87% rename from modules/dhcp/record/deleted_reservation.rb rename to modules/dhcp_common/record/deleted_reservation.rb index 428dd4756..bd1107e04 100644 --- a/modules/dhcp/record/deleted_reservation.rb +++ b/modules/dhcp_common/record/deleted_reservation.rb @@ -1,4 +1,4 @@ -require 'dhcp/record/reservation' +require 'dhcp_common/record/reservation' module Proxy::DHCP # represent a deleted DHCP Record diff --git a/modules/dhcp/record/lease.rb b/modules/dhcp_common/record/lease.rb similarity index 93% rename from modules/dhcp/record/lease.rb rename to modules/dhcp_common/record/lease.rb index 35aa7907f..a1dac3a69 100644 --- a/modules/dhcp/record/lease.rb +++ b/modules/dhcp_common/record/lease.rb @@ -1,4 +1,4 @@ -require 'dhcp/record' +require 'dhcp_common/record' module Proxy::DHCP # represent a DHCP Lease diff --git a/modules/dhcp/record/reservation.rb b/modules/dhcp_common/record/reservation.rb similarity index 94% rename from modules/dhcp/record/reservation.rb rename to modules/dhcp_common/record/reservation.rb index 357a7d973..915e898e2 100644 --- a/modules/dhcp/record/reservation.rb +++ b/modules/dhcp_common/record/reservation.rb @@ -1,4 +1,4 @@ -require 'dhcp/record' +require 'dhcp_common/record' module Proxy::DHCP # represent a DHCP Record diff --git a/modules/dhcp/server.rb b/modules/dhcp_common/server.rb similarity index 74% rename from modules/dhcp/server.rb rename to modules/dhcp_common/server.rb index f196363b7..5fbd14b86 100644 --- a/modules/dhcp/server.rb +++ b/modules/dhcp_common/server.rb @@ -1,11 +1,17 @@ -require "dhcp/subnet" -require "dhcp/record" -require "dhcp/record/lease" -require "dhcp/subnet_service" +require "dhcp_common/subnet" +require "dhcp_common/record" +require "dhcp_common/record/lease" +require "dhcp_common/record/reservation" +require 'dhcp_common/record/deleted_reservation' +require "dhcp_common/subnet_service" +require 'dhcp_common/dependency_injection/container' module Proxy::DHCP # represents a DHCP Server class Server + extend Proxy::DHCP::DependencyInjection::Injectors + + inject_attr :subnet_service, :service attr_reader :name alias_method :to_s, :name @@ -13,49 +19,47 @@ class Server include Proxy::Log include Proxy::Validations - def initialize(name, service) + def initialize(name) @name = name - @loaded = false - @service = service end def subnets - @service.all_subnets + service.all_subnets end # Abstracted Subnet loader method - def loadSubnets + def load_subnets logger.debug "Loading subnets for #{name}" end # Abstracted Subnet data loader method - def loadSubnetData subnet + def load_subnet_data subnet raise "Invalid Subnet" unless subnet.is_a? Proxy::DHCP::Subnet logger.debug "Loading subnet data for #{subnet}" end # Abstracted Subnet options loader method - def loadSubnetOptions subnet + def load_subnet_options subnet logger.debug "Loading Subnet options for #{subnet}" end def find_subnet subnet_address - @service.find_subnet(subnet_address) + service.find_subnet(subnet_address) end def all_leases(subnet) - @service.all_leases(subnet) + service.all_leases(subnet) end def all_hosts(subnet) - @service.all_hosts(subnet) + service.all_hosts(subnet) end def find_record(subnet_address, an_address) - @service.find_host_by_ip(subnet_address, an_address) || - @service.find_host_by_mac(subnet_address, an_address) || - @service.find_lease_by_ip(subnet_address, an_address) || - @service.find_lease_by_mac(subnet_address, an_address) + service.find_host_by_ip(subnet_address, an_address) || + service.find_host_by_mac(subnet_address, an_address) || + service.find_lease_by_ip(subnet_address, an_address) || + service.find_lease_by_mac(subnet_address, an_address) end def unused_ip(subnet, mac_address, from_address, to_address) @@ -71,8 +75,8 @@ def unused_ip(subnet, mac_address, from_address, to_address) end def ip_by_mac_address_and_range(subnet, mac_address, from_address, to_address) - r = @service.find_host_by_mac(subnet.network, mac_address) || - @service.find_lease_by_mac(subnet.network, mac_address) + r = service.find_host_by_mac(subnet.network, mac_address) || + service.find_lease_by_mac(subnet.network, mac_address) if r && subnet.valid_range(:from => from_address, :to => to_address).include?(r.ip) logger.debug "Found an existing dhcp record #{r}, reusing..." @@ -84,11 +88,11 @@ def inspect self end - # addRecord options can take a params hash from the API layer, which behaves + # add_record options can take a params hash from the API layer, which behaves # like a HashWithIndifferentAccess to symbol and string keys. # Delete keys with string names before adding them back with symbol names, # otherwise there will be duplicate information. - def addRecord options = {} + def add_record options = {} # dup the hash before modifying it locally options = options.dup options.delete("captures") @@ -109,7 +113,7 @@ def addRecord options = {} options.merge!(:hostname => hostname || name, :subnet => subnet, :ip => ip, :mac => mac) # try to figure out if we already have this record - record = @service.find_host_by_ip(subnet.network, ip) || @service.find_host_by_mac(subnet.network, mac) + record = service.find_host_by_ip(subnet.network, ip) || service.find_host_by_mac(subnet.network, mac) unless record.nil? if Record.compare_options(record.options, options) # we already got this record, no need to do anything @@ -136,7 +140,7 @@ def vendor_options_supported? # Default: manage any subnet. If specified: manage only specified subnets. def managed_subnet? subnet - managed_subnets = Proxy::DhcpPlugin.settings.dhcp_subnets + managed_subnets = Proxy::DhcpPlugin.settings.subnets return true unless managed_subnets managed_subnets.include? subnet end diff --git a/modules/dhcp/subnet.rb b/modules/dhcp_common/subnet.rb similarity index 97% rename from modules/dhcp/subnet.rb rename to modules/dhcp_common/subnet.rb index babc73743..645793b2d 100644 --- a/modules/dhcp/subnet.rb +++ b/modules/dhcp_common/subnet.rb @@ -1,7 +1,7 @@ require 'checks' require 'ipaddr' -require 'dhcp/monkey_patches' unless IPAddr.new.respond_to?('to_range') -require 'dhcp/monkey_patch_subnet' unless Array.new.respond_to?('rotate') +require 'dhcp_common/monkey_patches' unless IPAddr.new.respond_to?('to_range') +require 'dhcp_common/monkey_patch_subnet' unless Array.new.respond_to?('rotate') require 'proxy/validations' require 'socket' require 'timeout' diff --git a/modules/dhcp_common/subnet_service.rb b/modules/dhcp_common/subnet_service.rb new file mode 100644 index 000000000..21545ef7c --- /dev/null +++ b/modules/dhcp_common/subnet_service.rb @@ -0,0 +1,105 @@ +require 'dhcp_common/dependency_injection/container' + +class Proxy::DHCP::SubnetService + extend Proxy::DHCP::DependencyInjection::Injectors + include Proxy::Log + + inject_attr :memory_store, :subnets + inject_attr :memory_store, :leases_by_ip + inject_attr :memory_store, :leases_by_mac + inject_attr :memory_store, :reservations_by_ip + inject_attr :memory_store, :reservations_by_mac + inject_attr :memory_store, :reservations_by_name + + def add_subnet(subnet) + raise Proxy::DHCP::Error, "Unable to add subnet #{subnet}" if find_subnet(subnet.network) + logger.debug("Added a subnet: #{subnet.network}") + subnets[subnet.network] = subnet + end + + def add_subnets(*subnets) + subnets.each { |s| add_subnet(s) } + subnets + end + + def delete_subnet(subnet_address) + subnets.delete(subnet_address) + logger.debug("Deleted a subnet: #{subnet_address}") + end + + def find_subnet(address) + to_ret = subnets[address] + return to_ret if to_ret # we were given a subnet address + + # TODO: this can be done much faster + subnets.values.each do |subnet| + return subnet if subnet.include?(address) + end + + nil + end + + def all_subnets + subnets.values + end + + def add_lease(subnet_address, record) + leases_by_ip[subnet_address, record.ip] = record + leases_by_mac[subnet_address, record.mac] = record + logger.debug("Added a lease record: #{record.ip}:#{record.mac}") + end + + def add_host(subnet_address, record) + reservations_by_ip[subnet_address, record.ip] = record + reservations_by_mac[subnet_address, record.mac] = record + reservations_by_name[record.name] = record + logger.debug("Added a reservation: #{record.ip}:#{record.mac}:#{record.name}") + end + + def delete_lease(record) + leases_by_ip.delete(record.subnet.network, record.ip) + leases_by_mac.delete(record.subnet.network, record.mac) + logger.debug("Deleted a lease record: #{record.ip}:#{record.mac}") + end + + def delete_host(record) + reservations_by_ip.delete(record.subnet.network, record.ip) + reservations_by_mac.delete(record.subnet.network, record.mac) + reservations_by_name.delete(record.name) + logger.debug("Deleted a reservation: #{record.ip}:#{record.mac}:#{record.name}") + end + + def find_lease_by_mac(subnet_address, mac_address) + leases_by_mac[subnet_address, mac_address] + end + + def find_host_by_mac(subnet_address, mac_address) + reservations_by_mac[subnet_address, mac_address] + end + + def find_lease_by_ip(subnet_address, ip_address) + leases_by_ip[subnet_address, ip_address] + end + + def find_host_by_ip(subnet_address, ip_address) + reservations_by_ip[subnet_address, ip_address] + end + + def find_host_by_hostname(hostname) + return reservations_by_name[hostname] + end + + def all_hosts(subnet_address = nil) + if subnet_address + return reservations_by_ip[subnet_address] ? reservations_by_ip.values(subnet_address) : [] + end + reservations_by_ip.values + end + + def all_leases(subnet_address = nil) + if subnet_address + return leases_by_ip[subnet_address] ? leases_by_ip.values(subnet_address) : [] + end + leases_by_ip.values + end +end diff --git a/modules/dhcp_isc/dependencies.rb b/modules/dhcp_isc/dependencies.rb new file mode 100644 index 000000000..976c96950 --- /dev/null +++ b/modules/dhcp_isc/dependencies.rb @@ -0,0 +1,5 @@ +require 'dhcp_common/dependency_injection/dependencies' + +class Proxy::DHCP::DependencyInjection::Dependencies + dependency :dhcp_provider, Proxy::DHCP::ISC::Provider +end diff --git a/modules/dhcp_isc/dhcp_isc.rb b/modules/dhcp_isc/dhcp_isc.rb new file mode 100644 index 000000000..1a401e4e4 --- /dev/null +++ b/modules/dhcp_isc/dhcp_isc.rb @@ -0,0 +1,2 @@ +require 'dhcp_common/dhcp_common' +require 'dhcp_isc/dhcp_isc_plugin' diff --git a/modules/dhcp/providers/server/isc.rb b/modules/dhcp_isc/dhcp_isc_main.rb similarity index 75% rename from modules/dhcp/providers/server/isc.rb rename to modules/dhcp_isc/dhcp_isc_main.rb index 97582a71c..f5aad1701 100644 --- a/modules/dhcp/providers/server/isc.rb +++ b/modules/dhcp_isc/dhcp_isc_main.rb @@ -1,28 +1,40 @@ require 'time' -require 'dhcp/subnet' -require 'dhcp/record/deleted_reservation' -require 'dhcp/record/reservation' -require 'dhcp/record/lease' -require 'dhcp/server' - -module Proxy::DHCP - class ISC < Server +require 'dhcp_common/server' + +module Proxy::DHCP::ISC + class Provider < ::Proxy::DHCP::Server include Proxy::Util - def self.instance_with_default_parameters - Proxy::DHCP::ISC.new(:name => Proxy::DhcpPlugin.settings.dhcp_server, - :config => Proxy::DhcpPlugin.settings.dhcp_config, - :leases => Proxy::DhcpPlugin.settings.dhcp_leases, - :service => Proxy::DHCP::SubnetService.instance_with_default_parameters) + def initialize + super(Proxy::DhcpPlugin.settings.server) + @config_file = Proxy::DHCP::ISC::Plugin.settings.config + @leases_file = Proxy::DHCP::ISC::Plugin.settings.leases + # TODO: verify key name and secret + @key_name = Proxy::DHCP::ISC::Plugin.settings.key_name + @key_secret = Proxy::DHCP::ISC::Plugin.settings.key_secret + @omapi_port = Proxy::DHCP::ISC::Plugin.settings.omapi_port + end + + def initialize_for_testing(params) + @name = params[:name] || @name + @service = params[:service] || service + @config_file = params[:config_file] || @config_file + @leases_file = params[:leases_file] || @leases_file + @key_name = params[:key_name] || @key_name + @key_secret = params[:key_secret] || @key_secret + @omapi_port = params[:omapi_port] || @omapi_port + self end - def initialize options - super(options[:name], options[:service]) - @config = read_config(options[:config]).join("") - @leases = read_config(options[:leases], true).join("") + def config + @config ||= read_config(@config_file).join("") end - def delRecord subnet, record + def leases + @leases ||= read_config(@leases_file, true).join("") + end + + def del_record subnet, record validate_subnet subnet validate_record record raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable? @@ -35,7 +47,7 @@ def delRecord subnet, record omcmd("disconnect", msg) end - def addRecord options = {} + def add_record options = {} record = super(options) omcmd "connect" @@ -66,7 +78,7 @@ def parse_config_and_leases_for_records # Leases will have host and lease blocks. # Scan both together, in order, because host delete and lease end # events are appended linearly to the leases file. - conf = @config + @leases + conf = config + leases ret_val = [] # scan for host statements @@ -83,7 +95,7 @@ def parse_config_and_leases_for_records ret_val << Proxy::DHCP::DeletedReservation.new(opts) next end - subnet = @service.find_subnet(opts[:ip]) + subnet = service.find_subnet(opts[:ip]) next unless subnet ret_val << Proxy::DHCP::Reservation.new(opts.merge(:subnet => subnet)) end @@ -98,7 +110,7 @@ def parse_config_and_leases_for_records end next if opts[:mac].nil? - subnet = @service.find_subnet(ip) + subnet = service.find_subnet(ip) next unless subnet ret_val << Proxy::DHCP::Lease.new(opts.merge(:subnet => subnet, :ip => ip)) end @@ -111,38 +123,38 @@ def initialize_memory_store_with_dhcp_records(records) records.each do |record| case record when Proxy::DHCP::DeletedReservation - record = @service.find_host_by_hostname(record.name) - @service.delete_host(record) if record + record = service.find_host_by_hostname(record.name) + service.delete_host(record) if record next when Proxy::DHCP::Reservation - if dupe = @service.find_host_by_mac(record.subnet_address, record.mac) - @service.delete_host(dupe) + if dupe = service.find_host_by_mac(record.subnet_address, record.mac) + service.delete_host(dupe) end - if dupe = @service.find_host_by_ip(record.subnet_address, record.ip) - @service.delete_host(dupe) + if dupe = service.find_host_by_ip(record.subnet_address, record.ip) + service.delete_host(dupe) end - @service.add_host(record.subnet_address, record) + service.add_host(record.subnet_address, record) when Proxy::DHCP::Lease if record.options[:state] == "free" || (record.options[:next_state] == "free" && record.options[:ends] && record.options[:ends] < Time.now) - record = @service.find_lease_by_ip(record.subnet_address, record.ip) - @service.delete_lease(record) if record + record = service.find_lease_by_ip(record.subnet_address, record.ip) + service.delete_lease(record) if record next end - if dupe = @service.find_lease_by_mac(record.subnet_address, record.mac) - @service.delete_lease(dupe) + if dupe = service.find_lease_by_mac(record.subnet_address, record.mac) + service.delete_lease(dupe) end - if dupe = @service.find_lease_by_ip(record.subnet_address, record.ip) - @service.delete_lease(dupe) + if dupe = service.find_lease_by_ip(record.subnet_address, record.ip) + service.delete_lease(dupe) end - @service.add_lease(record.subnet_address, record) + service.add_lease(record.subnet_address, record) end end end - def loadSubnetData subnet + def load_subnet_data subnet initialize_memory_store_with_dhcp_records(parse_config_and_leases_for_records) end @@ -150,7 +162,7 @@ def loadSubnetData subnet def parse_config_for_subnets ret_val = [] # Extract subnets config block - @config.scan(SUBNET_BLOCK_REGEX) do |match| + config.scan(SUBNET_BLOCK_REGEX) do |match| network, netmask, subnet_config_lines = match ret_val << Proxy::DHCP::Subnet.new(network, netmask, parse_subnet_options(subnet_config_lines)) end @@ -176,49 +188,49 @@ def parse_subnet_options(subnet_config_lines) options.reject{|key, value| value.nil? || value.empty? } end - def loadSubnets + def load_subnets super - @service.add_subnets(*parse_config_for_subnets) + service.add_subnets(*parse_config_for_subnets) end def parse_record_options text options = {} case text # standard record values - when /^hardware\s+ethernet\s+(\S+)/ - options[:mac] = $1 - when /^fixed-address\s+(\S+)/ - options[:ip] = $1 - when /^next-server\s+(\S+)/ - options[:nextServer] = $1 - when /^filename\s+(\S+)/ - options[:filename] = $1 + when /^hardware\s+ethernet\s+(\S+)/ + options[:mac] = $1 + when /^fixed-address\s+(\S+)/ + options[:ip] = $1 + when /^next-server\s+(\S+)/ + options[:nextServer] = $1 + when /^filename\s+(\S+)/ + options[:filename] = $1 # Lease options - when /^binding\s+state\s+(\S+)/ - options[:state] = $1 - when /^next\s+binding\s+state\s+(\S+)/ - options[:next_state] = $1 - when /^starts\s+\d+\s+(.*)/ - options[:starts] = parse_time($1) - when /^ends\s+\d+\s+(.*)/ - options[:ends] = parse_time($1) + when /^binding\s+state\s+(\S+)/ + options[:state] = $1 + when /^next\s+binding\s+state\s+(\S+)/ + options[:next_state] = $1 + when /^starts\s+\d+\s+(.*)/ + options[:starts] = parse_time($1) + when /^ends\s+\d+\s+(.*)/ + options[:ends] = parse_time($1) # used for failover - not implemented - when /^tstp\s+\d+\s+(.*)/ - options[:tstp] = parse_time($1) + when /^tstp\s+\d+\s+(.*)/ + options[:tstp] = parse_time($1) # OMAPI settings - when /^deleted/ - options[:deleted] = true - when /^supersede server.next-server\s+=\s+(\S+)/ - begin - ns = validate_ip hex2ip($1) - rescue - ns = $1.gsub("\"","") - end - options[:nextServer] = ns - when /^supersede server.filename\s+=\s+"(\S+)"/ - options[:filename] = $1 - when "dynamic" - options[:deleteable] = true + when /^deleted/ + options[:deleted] = true + when /^supersede server.next-server\s+=\s+(\S+)/ + begin + ns = validate_ip hex2ip($1) + rescue + ns = $1.gsub("\"","") + end + options[:nextServer] = ns + when /^supersede server.filename\s+=\s+"(\S+)"/ + options[:filename] = $1 + when "dynamic" + options[:deleteable] = true #TODO: check if adding a new reservation with omshell for a free lease still #generates a conflict end @@ -230,9 +242,9 @@ def omcmd cmd, msg=nil if cmd == "connect" om_binary = which("omshell") @om = IO.popen("/bin/sh -c '#{om_binary} 2>&1'", "r+") - @om.puts "key #{Proxy::DhcpPlugin.settings.dhcp_key_name} \"#{Proxy::DhcpPlugin.settings.dhcp_key_secret}\"" if Proxy::DhcpPlugin.settings.dhcp_key_name && Proxy::DhcpPlugin.settings.dhcp_key_secret + @om.puts "key #{@key_name} \"#{@key_secret}\"" if @key_name && @key_secret @om.puts "server #{name}" - @om.puts "port #{Proxy::DhcpPlugin.settings.dhcp_omapi_port}" + @om.puts "port #{@omapi_port}" @om.puts "connect" @om.puts "new host" elsif cmd == "disconnect" @@ -304,7 +316,7 @@ def filter_log log def read_config file, ignore_includes=false logger.debug "Reading config file #{file}" - config = [] + to_return = [] File.readlines(file).each do |line| line = line.split('#').first.strip # remove comments, left and right whitespace next if line.empty? # remove blank lines @@ -317,12 +329,12 @@ def read_config file, ignore_includes=false end # concat modifies the receiver rather than creating a new array # and does not create a multidimensional array - config.concat(read_config(conf, ignore_includes)) + to_return.concat(read_config(conf, ignore_includes)) else - config << line + to_return << line end end - config + to_return end def vendor_options_supported? @@ -424,15 +436,15 @@ def poap_options_statements(options) end statements end - end - # Get all IPv4 addresses provided by the ISC DHCP config line following the pattern "my-config-option-or-directive IPv4_ADDR[, IPv4_ADDR] [...];" and return an array. - def get_ip_list_from_config_line(option_line) - option_line.scan(/\s*((?:(?:\d{1,3}\.){3}\d{1,3})\s*(?:,\s*(?:(?:\d{1,3}\.){3}\d{1,3})\s*)*)/).first.first.gsub(/\s/,'').split(",").reject{|value| value.nil? || value.empty? } - end + # Get all IPv4 addresses provided by the ISC DHCP config line following the pattern "my-config-option-or-directive IPv4_ADDR[, IPv4_ADDR] [...];" and return an array. + def get_ip_list_from_config_line(option_line) + option_line.scan(/\s*((?:(?:\d{1,3}\.){3}\d{1,3})\s*(?:,\s*(?:(?:\d{1,3}\.){3}\d{1,3})\s*)*)/).first.first.gsub(/\s/,'').split(",").reject{|value| value.nil? || value.empty? } + end - # Get IPv4 range provided by the ISC DHCP config line following the pattern "range IPv4_ADDR IPv4_ADDR;" and return an array. - def get_range_from_config_line(range_line) - range_line.scan(/\s*((?:\d{1,3}\.){3}\d{1,3})\s*((?:\d{1,3}\.){3}\d{1,3})\s*/).first + # Get IPv4 range provided by the ISC DHCP config line following the pattern "range IPv4_ADDR IPv4_ADDR;" and return an array. + def get_range_from_config_line(range_line) + range_line.scan(/\s*((?:\d{1,3}\.){3}\d{1,3})\s*((?:\d{1,3}\.){3}\d{1,3})\s*/).first + end end end diff --git a/modules/dhcp_isc/dhcp_isc_plugin.rb b/modules/dhcp_isc/dhcp_isc_plugin.rb new file mode 100644 index 000000000..1397cdb12 --- /dev/null +++ b/modules/dhcp_isc/dhcp_isc_plugin.rb @@ -0,0 +1,17 @@ +module ::Proxy::DHCP::ISC + class Plugin < ::Proxy::Provider + # :provider_class is optional and can be omitted in this case, as it follows the naming convention: "plugin namespace::Provider" + plugin :dhcp_isc, ::Proxy::VERSION, :provider_class => "::Proxy::DHCP::ISC::Provider" + + default_settings :config => '/etc/dhcp/dhcpd.conf', :leases => '/var/lib/dhcpd/dhcpd.leases', + :omapi_port => '7911' + + requires :dhcp, ::Proxy::VERSION + validate_readable :config, :leases + + after_activation do + require 'dhcp_isc/dhcp_isc_main' + require 'dhcp_isc/dependencies' + end + end +end diff --git a/modules/dhcp_native_ms/dependencies.rb b/modules/dhcp_native_ms/dependencies.rb new file mode 100644 index 000000000..725f0d7c0 --- /dev/null +++ b/modules/dhcp_native_ms/dependencies.rb @@ -0,0 +1,5 @@ +require 'dhcp_common/dependency_injection/dependencies' + +class Proxy::DHCP::DependencyInjection::Dependencies + dependency :dhcp_provider, Proxy::DHCP::NativeMS::Provider +end diff --git a/modules/dhcp_native_ms/dhcp_native_ms.rb b/modules/dhcp_native_ms/dhcp_native_ms.rb new file mode 100644 index 000000000..79bf1c9ef --- /dev/null +++ b/modules/dhcp_native_ms/dhcp_native_ms.rb @@ -0,0 +1,2 @@ +require 'dhcp_common/dhcp_common' +require 'dhcp_native_ms/dhcp_native_ms_plugin' diff --git a/modules/dhcp/providers/server/native_ms.rb b/modules/dhcp_native_ms/dhcp_native_ms_main.rb similarity index 85% rename from modules/dhcp/providers/server/native_ms.rb rename to modules/dhcp_native_ms/dhcp_native_ms_main.rb index 6c8d280da..b01a1d52c 100644 --- a/modules/dhcp/providers/server/native_ms.rb +++ b/modules/dhcp_native_ms/dhcp_native_ms_main.rb @@ -1,26 +1,23 @@ require 'checks' require 'open3' -require 'dhcp/subnet' -require 'dhcp/record/reservation' -require 'dhcp/record/lease' -require 'dhcp/server' +require 'dhcp_common/server' -module Proxy::DHCP +module Proxy::DHCP::NativeMS # Represents Microsoft DHCP Server manipulated via the netsh command # executed on a Microsoft server under a service account - class NativeMS < Server + class Provider < ::Proxy::DHCP::Server - def self.instance_with_default_parameters - Proxy::DHCP::NativeMS.new( - :server => Proxy::DhcpPlugin.settings.dhcp_server ? Proxy::DhcpPlugin.settings.dhcp_server : "127.0.0.1", - :service => Proxy::DHCP::SubnetService.instance_with_default_parameters) + def initialize_for_testing(params) + @name = params[:name] || @name + @service = params[:service] || service + self end - def initialize(options = {}) - super options[:server], options[:service] + def initialize + super(Proxy::DhcpPlugin.settings.server) end - def delRecord subnet, record + def del_record subnet, record validate_subnet subnet validate_record record # TODO: Refactor this into the base class @@ -33,7 +30,7 @@ def delRecord subnet, record execute(cmd, msg) end - def addRecord options={} + def add_record options={} record = super(options) cmd = "scope #{record.subnet.network} add reservedip #{record.ip} #{record.mac.gsub(/:/,"")} #{record.name}" @@ -125,20 +122,20 @@ def find_subnet_dhcp_records(subnet) to_return end - def loadSubnetData subnet + def load_subnet_data subnet super records = find_subnet_dhcp_records(subnet) records.each do |record| case record - when Proxy::DHCP::Reservation - @service.add_host(record.subnet_address, record) - when Proxy::DHCP::Lease - @service.add_lease(record.subnet_address, record) + when Proxy::DHCP::Reservation + service.add_host(record.subnet_address, record) + when Proxy::DHCP::Lease + service.add_lease(record.subnet_address, record) end end end - def loadSubnetOptions subnet + def load_subnet_options subnet super subnet raise "invalid Subnet" unless subnet.is_a? Proxy::DHCP::Subnet cmd = "scope #{subnet.network} Show OptionValue" @@ -192,9 +189,9 @@ def find_all_subnets ret_val end - def loadSubnets + def load_subnets super - @service.add_subnets(*find_all_subnets) + service.add_subnets(*find_all_subnets) end def execute cmd, msg=nil, error_only=false @@ -257,17 +254,17 @@ def parse_options response break if line.match(/^Command completed/) case line - #TODO: this logic is broken, as the output reports only once the vendor type - # making it impossible to detect if its a standard option or a custom one. - when /For vendor class \[([^\]]+)\]:/ - options[:vendor] = "<#{$1}>" - when /OptionId : (\d+)/ - optionId = $1.to_i - when /Option Element Value = (\S+)/ - #TODO move options to a class or something - opts = SUNW.update(Standard) - title = opts.select {|k,v| v[:code] == optionId}.flatten[0] - options[title] = $1 if title + #TODO: this logic is broken, as the output reports only once the vendor type + # making it impossible to detect if its a standard option or a custom one. + when /For vendor class \[([^\]]+)\]:/ + options[:vendor] = "<#{$1}>" + when /OptionId : (\d+)/ + optionId = $1.to_i + when /Option Element Value = (\S+)/ + #TODO move options to a class or something + opts = SUNW.update(Standard) + title = opts.select {|k,v| v[:code] == optionId}.flatten[0] + options[title] = $1 if title end end logger.debug options.inspect @@ -282,10 +279,10 @@ def parse_classes response break if line.match(/^Command completed/) case line - when /Class \[([^\]]+)\]:/ - klass = $1 - when /Isvendor= TRUE/ - classes << klass + when /Class \[([^\]]+)\]:/ + klass = $1 + when /Isvendor= TRUE/ + classes << klass end end logger.debug "found the following classes: #{classes.join(", ")}" @@ -295,6 +292,5 @@ def parse_classes response def vendor_options_supported? true end - end end diff --git a/modules/dhcp_native_ms/dhcp_native_ms_plugin.rb b/modules/dhcp_native_ms/dhcp_native_ms_plugin.rb new file mode 100644 index 000000000..a388ab298 --- /dev/null +++ b/modules/dhcp_native_ms/dhcp_native_ms_plugin.rb @@ -0,0 +1,12 @@ +module ::Proxy::DHCP::NativeMS + class Plugin < ::Proxy::Provider + plugin :dhcp_native_ms, ::Proxy::VERSION + + requires :dhcp, ::Proxy::VERSION + + after_activation do + require 'dhcp_native_ms/dhcp_native_ms_main' + require 'dhcp_native_ms/dependencies' + end + end +end diff --git a/modules/dhcp_virsh/dependencies.rb b/modules/dhcp_virsh/dependencies.rb new file mode 100644 index 000000000..661bf7758 --- /dev/null +++ b/modules/dhcp_virsh/dependencies.rb @@ -0,0 +1,5 @@ +require 'dhcp_common/dependency_injection/dependencies' + +class Proxy::DHCP::DependencyInjection::Dependencies + dependency :dhcp_provider, Proxy::DHCP::Virsh::Provider +end diff --git a/modules/dhcp_virsh/dhcp_virsh.rb b/modules/dhcp_virsh/dhcp_virsh.rb new file mode 100644 index 000000000..6968fdfe2 --- /dev/null +++ b/modules/dhcp_virsh/dhcp_virsh.rb @@ -0,0 +1,2 @@ +require 'dhcp_common/dhcp_common' +require 'dhcp_virsh/dhcp_virsh_plugin' diff --git a/modules/dhcp/providers/server/virsh.rb b/modules/dhcp_virsh/dhcp_virsh_main.rb similarity index 73% rename from modules/dhcp/providers/server/virsh.rb rename to modules/dhcp_virsh/dhcp_virsh_main.rb index 3abf0fe24..9d9afa1c4 100644 --- a/modules/dhcp/providers/server/virsh.rb +++ b/modules/dhcp_virsh/dhcp_virsh_main.rb @@ -1,29 +1,28 @@ require 'proxy/virsh' require 'rexml/document' require 'ipaddr' -require 'dhcp/subnet' -require 'dhcp/record/reservation' -require 'dhcp/server' +require 'dhcp_common/server' -module Proxy::DHCP - class Virsh < Server +module Proxy::DHCP::Virsh + class Provider < ::Proxy::DHCP::Server include Proxy::Virsh - def self.instance_with_default_parameters - Proxy::DHCP::Virsh.new(:virsh_network => Proxy::SETTINGS.virsh_network, - :service => Proxy::DHCP::SubnetService.instance_with_default_parameters) + def initialize + super("127.0.0.1") + @network = Proxy::SETTINGS.virsh_network end - def initialize options - @network = options[:virsh_network] - raise "DNS virsh provider needs 'virsh_network' option" unless network - super(options[:name], options[:service]) + def initialize_for_testing(params) + @name = params[:name] || @name + @service = params[:service] || service + @network = params[:network] || @network + self end # we support only one subnet - def loadSubnets + def load_subnets super - @service.add_subnets(*parse_config_for_subnets) + service.add_subnets(*parse_config_for_subnets) end def parse_config_for_subnets @@ -60,7 +59,7 @@ def parse_config_for_dhcp_records(subnet) doc = REXML::Document.new xml = dump_xml REXML::XPath.each(doc, "//network/ip[not(@family) or @family='ipv4']/dhcp/host") do |e| to_ret << Proxy::DHCP::Reservation.new(:subnet => subnet, :ip => e.attributes["ip"], - :mac => e.attributes["mac"], :hostname => e.attributes["name"]) + :mac => e.attributes["mac"], :hostname => e.attributes["name"]) end rescue Exception => e msg = "DHCP virsh provider error: unable to retrive virsh info: #{e}" @@ -71,19 +70,19 @@ def parse_config_for_dhcp_records(subnet) to_ret end - def loadSubnetData subnet + def load_subnet_data subnet super(subnet) records = parse_config_for_dhcp_records(subnet) - records.each { |record| @service.add_host(record.subnet_address, record) } + records.each { |record| service.add_host(record.subnet_address, record) } end - def addRecord options={} + def add_record options={} record = super(options) virsh_update_dhcp 'add-last', record.mac, record.ip, record.name record end - def delRecord subnet, record + def del_record subnet, record virsh_update_dhcp 'delete', record.mac, record.ip, record[:hostname] end @@ -103,6 +102,5 @@ def virsh_update_dhcp command, mac, ip, name rescue Proxy::Virsh::Error => e raise Proxy::DHCP::Error, "Failed to update DHCP: #{e}" end - end end diff --git a/modules/dhcp_virsh/dhcp_virsh_plugin.rb b/modules/dhcp_virsh/dhcp_virsh_plugin.rb new file mode 100644 index 000000000..9b7252990 --- /dev/null +++ b/modules/dhcp_virsh/dhcp_virsh_plugin.rb @@ -0,0 +1,12 @@ +module ::Proxy::DHCP::Virsh + class Plugin < ::Proxy::Provider + plugin :dhcp_virsh, ::Proxy::VERSION + + requires :dhcp, ::Proxy::VERSION + + after_activation do + require 'dhcp_virsh/dhcp_virsh_main' + require 'dhcp_virsh/dependencies' + end + end +end diff --git a/test/dhcp/dhcp_api_test.rb b/test/dhcp/dhcp_api_test.rb index 1dbc00f69..22c1c0fce 100644 --- a/test/dhcp/dhcp_api_test.rb +++ b/test/dhcp/dhcp_api_test.rb @@ -3,8 +3,9 @@ require 'ostruct' require 'dhcp/dhcp' +require 'dhcp_common/dependency_injection/container' require 'dhcp/dhcp_api' -require 'dhcp/providers/server/isc' +require 'dhcp_isc/dhcp_isc' require 'dhcp/sparc_attrs' ENV['RACK_ENV'] = 'test' @@ -20,12 +21,17 @@ def app def setup Proxy::DhcpPlugin.load_test_settings( :enabled => true, - :dhcp_vendor => 'isc', - :dhcp_config => './test/fixtures/dhcp/dhcp.conf', - :dhcp_leases => './test/fixtures/dhcp/dhcp.leases', + :use_provider => 'dhcp_isc', :dhcp_subnets => '192.168.122.0/255.255.255.0') - Proxy::DHCP::Server::ISC.any_instance.stubs(:omcmd) + Proxy::DHCP::ISC::Plugin.load_test_settings( + :config => './test/fixtures/dhcp/dhcp.conf', + :leases => './test/fixtures/dhcp/dhcp.leases') + + Proxy::Plugins.configure_loaded_plugins + Proxy::DHCP::ISC::Plugin.new.configure_plugin + + Proxy::DHCP::ISC::Provider.any_instance.stubs(:omcmd) end # Date formats change between Ruby versions and JSON libraries & versions @@ -214,7 +220,7 @@ def test_api_10_delete_record_notfound def test_sparc_host_creation sub = Proxy::DHCP::Subnet.new('192.168.122.0','255.255.255.0') - Proxy::DHCP::Server::ISC.any_instance.stubs(:find_subnet).returns(sub) + Proxy::DHCP::ISC::Provider.any_instance.stubs(:find_subnet).returns(sub) post '/192.168.122.10', sparc_attrs assert last_response.ok?, "Last response was not ok: #{last_response.body}" diff --git a/test/dhcp/dhcp_config_test.rb b/test/dhcp/dhcp_config_test.rb index 942bf6895..a532392d1 100644 --- a/test/dhcp/dhcp_config_test.rb +++ b/test/dhcp/dhcp_config_test.rb @@ -4,8 +4,7 @@ class DhcpConfigTest < Test::Unit::TestCase def test_omitted_settings_have_default_values Proxy::DhcpPlugin.load_test_settings({}) - assert_equal 'isc', Proxy::DhcpPlugin.settings.dhcp_provider - assert_equal '127.0.0.1', Proxy::DhcpPlugin.settings.dhcp_server - assert_equal '7911', Proxy::DhcpPlugin.settings.dhcp_omapi_port + assert_equal '127.0.0.1', Proxy::DhcpPlugin.settings.server + assert_equal 'dhcp_isc', Proxy::DhcpPlugin.settings.use_provider end end diff --git a/test/dhcp/record_test.rb b/test/dhcp/record_test.rb index cda485b18..13792f09a 100644 --- a/test/dhcp/record_test.rb +++ b/test/dhcp/record_test.rb @@ -1,9 +1,6 @@ require 'test_helper' - -require 'dhcp/dhcp' -require 'dhcp/server' -require 'dhcp/subnet' -require 'dhcp/record' +require 'dhcp_common/dhcp_common' +require 'dhcp_common/server' class Proxy::DHCPRecordTest < Test::Unit::TestCase diff --git a/test/dhcp/server_test.rb b/test/dhcp/server_test.rb index d8c4e6d06..e8f4572c0 100644 --- a/test/dhcp/server_test.rb +++ b/test/dhcp/server_test.rb @@ -1,17 +1,14 @@ require 'test_helper' -require "dhcp/dhcp" -require 'dhcp/server' -require 'dhcp/subnet' -require 'dhcp/record' -require 'dhcp/record/reservation' +require 'dhcp_common/dhcp_common' +require 'dhcp_common/server' +require 'dhcp_common/dependency_injection/dependencies' class DHCPServerTest < Test::Unit::TestCase def setup - @service = Proxy::DHCP::SubnetService.new(Proxy::MemoryStore.new, Proxy::MemoryStore.new, - Proxy::MemoryStore.new, Proxy::MemoryStore.new, - Proxy::MemoryStore.new, Proxy::MemoryStore.new) - @server = Proxy::DHCP::Server.new("testcase", @service) + @service = Proxy::DHCP::SubnetService.new + @server = Proxy::DHCP::Server.new("testcase") + @server.service = @service @subnet = Proxy::DHCP::Subnet.new("192.168.0.0", "255.255.255.0") @service.add_subnet(@subnet) @@ -26,13 +23,13 @@ def test_should_provide_subnets def test_should_raise_exception_when_record_exists assert_raises Proxy::DHCP::AlreadyExists do - @server.addRecord('hostname' => 'test', 'network' => @subnet.network, 'ip' => "192.168.0.11", 'mac' => "aa:bb:cc:dd:ee:ff") + @server.add_record('hostname' => 'test', 'network' => @subnet.network, 'ip' => "192.168.0.11", 'mac' => "aa:bb:cc:dd:ee:ff") end end def test_should_raise_exception_when_address_in_use assert_raises Proxy::DHCP::Collision do - @server.addRecord('hostname' => 'test-1', 'network' => @subnet.network, 'ip' => "192.168.0.11", 'mac' => "aa:bb:cc:dd:ee:ef") + @server.add_record('hostname' => 'test-1', 'network' => @subnet.network, 'ip' => "192.168.0.11", 'mac' => "aa:bb:cc:dd:ee:ef") end end @@ -65,6 +62,19 @@ def test_ip_by_mac_address_and_range_should_return_nil_when_range_ip_is_outside_ assert_nil @server.ip_by_mac_address_and_range(@subnet, "aa:bb:cc:dd:ee:ff", "192.168.0.100", "192.168.0.120") end + def test_managed_subnet + ::Proxy::DhcpPlugin.load_test_settings(:subnets => ['192.168.1.0/255.255.255.0', '192.168.2.0/255.255.255.0']) + + assert @server.managed_subnet?('192.168.1.0/255.255.255.0') + assert @server.managed_subnet?('192.168.2.0/255.255.255.0') + assert !@server.managed_subnet?('192.168.3.0/255.255.255.0') + end + + def test_managed_subnet_should_return_true_when_setting_is_undefined + ::Proxy::DhcpPlugin.load_test_settings({}) + assert @server.managed_subnet?('192.168.1.0/255.255.255.0') + end + def test_should_have_a_name assert !@server.name.nil? end diff --git a/test/dhcp/subnet_service_test.rb b/test/dhcp/subnet_service_test.rb index bac05af8b..dd500e876 100644 --- a/test/dhcp/subnet_service_test.rb +++ b/test/dhcp/subnet_service_test.rb @@ -1,22 +1,17 @@ require 'test_helper' - -require 'dhcp/dhcp' -require 'dhcp/subnet' -require 'dhcp/record/lease' -require 'dhcp/record/reservation' -require 'dhcp/subnet_service' +require 'dhcp_common/dhcp_common' +require 'dhcp_common/server' class SubnetServiceTest < Test::Unit::TestCase def setup - @subnets = Proxy::MemoryStore.new - @leases_ip_store = Proxy::MemoryStore.new - @leases_mac_store = Proxy::MemoryStore.new - @reservations_ip_store = Proxy::MemoryStore.new - @reservations_mac_store = Proxy::MemoryStore.new - @reservations_name_store = Proxy::MemoryStore.new - - @service = Proxy::DHCP::SubnetService.new(@subnets, @leases_ip_store, @leases_mac_store, @reservations_ip_store, - @reservations_mac_store, @reservations_name_store) + @service = Proxy::DHCP::SubnetService.new + + @subnets = @service.subnets = Proxy::MemoryStore.new + @leases_ip_store = @service.leases_by_ip = Proxy::MemoryStore.new + @leases_mac_store = @service.leases_by_mac = Proxy::MemoryStore.new + @reservations_ip_store = @service.reservations_by_ip = Proxy::MemoryStore.new + @reservations_mac_store = @service.reservations_by_mac = Proxy::MemoryStore.new + @reservations_name_store = @service.reservations_by_name = Proxy::MemoryStore.new end def test_add_subnet diff --git a/test/dhcp/subnet_test.rb b/test/dhcp/subnet_test.rb index a1a68941a..f228c4b53 100644 --- a/test/dhcp/subnet_test.rb +++ b/test/dhcp/subnet_test.rb @@ -1,8 +1,6 @@ require 'test_helper' -require "dhcp/dhcp" -require 'dhcp/server' -require 'dhcp/subnet' -require 'dhcp/record/reservation' +require 'dhcp_common/dhcp_common' +require 'dhcp_common/server' class Proxy::DHCPSubnetTest < Test::Unit::TestCase def setup diff --git a/test/dhcp_isc/dhcp_isc_config_test.rb b/test/dhcp_isc/dhcp_isc_config_test.rb new file mode 100644 index 000000000..3fb619a39 --- /dev/null +++ b/test/dhcp_isc/dhcp_isc_config_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' +require 'dhcp_isc/dhcp_isc' + +class DhcpIscConfigTest < ::Test::Unit::TestCase + def test_default_configuration + Proxy::DHCP::ISC::Plugin.load_test_settings({}) + assert_equal '7911', Proxy::DHCP::ISC::Plugin.settings.omapi_port + assert_equal '/etc/dhcp/dhcpd.conf', Proxy::DHCP::ISC::Plugin.settings.config + assert_equal '/var/lib/dhcpd/dhcpd.leases', Proxy::DHCP::ISC::Plugin.settings.leases + end +end \ No newline at end of file diff --git a/test/dhcp_isc/dhcp_isc_provider_interface_test.rb b/test/dhcp_isc/dhcp_isc_provider_interface_test.rb new file mode 100644 index 000000000..f4b163fbd --- /dev/null +++ b/test/dhcp_isc/dhcp_isc_provider_interface_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' +require 'dhcp_isc/dhcp_isc_main' + +class IscDhcpProviderInterfaceTest < Test::Unit::TestCase + def test_provider_interface + assert_dhcp_provider_interface(Proxy::DHCP::ISC::Provider) + end +end diff --git a/test/dhcp/server_isc_test.rb b/test/dhcp_isc/server_isc_test.rb similarity index 75% rename from test/dhcp/server_isc_test.rb rename to test/dhcp_isc/server_isc_test.rb index 901f98608..861250d18 100644 --- a/test/dhcp/server_isc_test.rb +++ b/test/dhcp_isc/server_isc_test.rb @@ -2,7 +2,9 @@ require 'dhcp/sparc_attrs' require 'json' require 'dhcp/dhcp' -require 'dhcp/providers/server/isc' +require 'dhcp_isc/dhcp_isc' +require 'dhcp_isc/dhcp_isc_main' +require 'dhcp_common/dependency_injection/dependencies' class ServerIscTest < Test::Unit::TestCase class OMIO @@ -20,31 +22,42 @@ def puts str include SparcAttrs def setup - Proxy::DhcpPlugin.load_test_settings( - :enabled => true, - :dhcp_vendor => 'isc', - :dhcp_omapi_port => 999, - :dhcp_config => './test/fixtures/dhcp/dhcp.conf', - :dhcp_leases => './test/fixtures/dhcp/dhcp.leases', - :dhcp_subnets => '192.168.122.0/255.255.255.0') - - @subnet_service = Proxy::DHCP::SubnetService.new(Proxy::MemoryStore.new, Proxy::MemoryStore.new, - Proxy::MemoryStore.new, Proxy::MemoryStore.new, - Proxy::MemoryStore.new, Proxy::MemoryStore.new) - @dhcp = Proxy::DHCP::Server::ISC.new( - :name => '192.168.122.1', :config => './test/fixtures/dhcp/dhcp.conf', - :leases => './test/fixtures/dhcp/dhcp.leases', - :service => @subnet_service) + ::Proxy::DhcpPlugin.load_test_settings({}) + ::Proxy::DHCP::ISC::Plugin.load_test_settings({}) + + @subnet_service = Proxy::DHCP::SubnetService.new + @dhcp = Proxy::DHCP::ISC::Provider.new.initialize_for_testing( + :name => '192.168.122.1', :config_file => './test/fixtures/dhcp/dhcp.conf', + :leases_file => './test/fixtures/dhcp/dhcp.leases', + :service => @subnet_service, :omapi_port => 999) + end + + class DhcpIscProviderForTesting < ::Proxy::DHCP::ISC::Provider + attr_reader :config_file, :leases_file, :key_name, :key_secret, :omapi_port + end + + def test_isc_provider_initialization + ::Proxy::DhcpPlugin.load_test_settings(:server => 'a_server') + ::Proxy::DHCP::ISC::Plugin.load_test_settings(:config => 'config_file', :leases => 'leases_file', + :omapi_port => '7777', :key_name => 'key_name', + :key_secret => 'key_secret') + + provider = DhcpIscProviderForTesting.new + assert_equal 'a_server', provider.name + assert_equal 'config_file', provider.config_file + assert_equal 'leases_file', provider.leases_file + assert_equal '7777', provider.omapi_port + assert_equal 'key_name', provider.key_name + assert_equal 'key_secret', provider.key_secret end def test_omcmd_server_connect - srv = Proxy::DHCP::ISC.new :name => '1.2.3.4', :config => './test/fixtures/dhcp/dhcp.conf', :leases => './test/fixtures/dhcp/dhcp.leases' - srv.stubs(:which).returns('fakeshell') + @dhcp.stubs(:which).returns('fakeshell') omio = OMIO.new IO.expects(:popen).with("/bin/sh -c 'fakeshell 2>&1'", "r+").returns(omio) - srv.send(:omcmd, 'connect') + @dhcp.omcmd('connect') assert_equal "port 999", omio.input_commands[1] - assert_equal "server 1.2.3.4", omio.input_commands[0] + assert_equal "server 192.168.122.1", omio.input_commands[0] end def test_sparc_host_quirks @@ -80,43 +93,43 @@ def test_poap_quirks end def test_subnet_matching_without_parameters_or_declarations - assert "subnet 192.168.1.0 netmask 255.255.255.128 {}".match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + assert "subnet 192.168.1.0 netmask 255.255.255.128 {}".match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end def test_subnet_matching_with_ip_parameter - assert "subnet 192.168.123.0 netmask 255.255.255.192 {option subnet-mask 255.255.255.192;}".match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + assert "subnet 192.168.123.0 netmask 255.255.255.192 {option subnet-mask 255.255.255.192;}".match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end def test_subnet_matching_with_numerical_parameter - assert "subnet 192.168.123.0 netmask 255.255.255.192 {adaptive-lease-time-threshold 50;}".match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + assert "subnet 192.168.123.0 netmask 255.255.255.192 {adaptive-lease-time-threshold 50;}".match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end def test_subnet_matching_with_timestamp_parameter - assert "subnet 192.168.123.0 netmask 255.255.255.192 {dynamic-bootp-lease-cutoff 5 2016/11/11 01:01:00;}".match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + assert "subnet 192.168.123.0 netmask 255.255.255.192 {dynamic-bootp-lease-cutoff 5 2016/11/11 01:01:00;}".match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end def test_subnet_matching_with_string_parameter - assert "subnet 192.168.123.0 netmask 255.255.255.192 {filename \"filename\";}".match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + assert "subnet 192.168.123.0 netmask 255.255.255.192 {filename \"filename\";}".match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end def test_subnet_matching_with_spaces_in_parameter - assert "subnet 192.168.123.0 netmask 255.255.255.192 { option subnet-mask 255.255.255.192 ; }".match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + assert "subnet 192.168.123.0 netmask 255.255.255.192 { option subnet-mask 255.255.255.192 ; }".match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end def test_subnet_matching_with_declaration - assert "subnet 192.168.123.0 netmask 255.255.255.192 {pool{range 192.168.42.200 192.168.42.254;}}".match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + assert "subnet 192.168.123.0 netmask 255.255.255.192 {pool{range 192.168.42.200 192.168.42.254;}}".match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end def test_subnet_matching_with_declaration_and_parameter assert "subnet 192.168.123.0 netmask 255.255.255.192 {pool{range 192.168.42.200 192.168.42.254;}option subnet-mask 255.255.255.192;}". - match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end def test_mathing_with_spaces_in_declarations - assert "subnet 192.168.1.0 netmask 255.255.255.128 { pool\n{ range abc ; } }".match(Proxy::DHCP::ISC::SUBNET_BLOCK_REGEX) + assert "subnet 192.168.1.0 netmask 255.255.255.128 { pool\n{ range abc ; } }".match(Proxy::DHCP::ISC::Provider::SUBNET_BLOCK_REGEX) end - def test_loadSubnets_loads_managed_subnets + def test_load_subnets_loads_managed_subnets subnets = @dhcp.parse_config_for_subnets assert_equal 4, subnets.size end @@ -171,7 +184,7 @@ def test_parse_config_and_leases subnet = Proxy::DHCP::Subnet.new("192.168.122.0", "255.255.255.0") @subnet_service.add_subnet(subnet) - @dhcp.loadSubnetData(subnet) + @dhcp.load_subnet_data(subnet) assert_equal 8, @subnet_service.all_hosts("192.168.122.0").size + @subnet_service.all_leases("192.168.122.0").size assert_nil @subnet_service.find_host_by_hostname("deleted.example.com") diff --git a/test/dhcp_ms_native/dhcp_ms_native_provider_interface_test.rb b/test/dhcp_ms_native/dhcp_ms_native_provider_interface_test.rb new file mode 100644 index 000000000..d81dde49f --- /dev/null +++ b/test/dhcp_ms_native/dhcp_ms_native_provider_interface_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' +require 'dhcp_native_ms/dhcp_native_ms_main' + +class MsNativeDhcpProviderInterfaceTest < Test::Unit::TestCase + def test_provider_interface + assert_dhcp_provider_interface(Proxy::DHCP::NativeMS::Provider) + end +end diff --git a/test/dhcp/server_ms_test.rb b/test/dhcp_ms_native/server_ms_test.rb similarity index 88% rename from test/dhcp/server_ms_test.rb rename to test/dhcp_ms_native/server_ms_test.rb index 9c6767cd3..169b03fb5 100644 --- a/test/dhcp/server_ms_test.rb +++ b/test/dhcp_ms_native/server_ms_test.rb @@ -1,18 +1,18 @@ require 'test_helper' - require 'dhcp/dhcp' -require 'dhcp/providers/server/native_ms' +require 'dhcp_native_ms/dhcp_native_ms' +require 'dhcp_native_ms/dhcp_native_ms_main' +require 'dhcp/sparc_attrs' +require 'dhcp_common/dependency_injection/dependencies' class DHCPServerMicrosoftTest < Test::Unit::TestCase # rubocop:disable Metrics/MethodLength def setup - @subnet_service = Proxy::DHCP::SubnetService.new(Proxy::MemoryStore.new, Proxy::MemoryStore.new, - Proxy::MemoryStore.new, Proxy::MemoryStore.new, - Proxy::MemoryStore.new, Proxy::MemoryStore.new) - - @server = Proxy::DHCP::Server::NativeMS.new(:server => "1.2.3.4", - :service => @subnet_service) + ::Proxy::DhcpPlugin.load_test_settings({}) + @subnet_service = Proxy::DHCP::SubnetService.new + @server = Proxy::DHCP::NativeMS::Provider.new.initialize_for_testing(:name => "1.2.3.4", + :service => @subnet_service) @server.stubs(:execute).with("show scope", "Enumerated the scopes on 1.2.3.4").returns(' ============================================================================== @@ -148,7 +148,12 @@ def setup Option Element Value = brslcs25 Command completed successfully. ') - @server.loadSubnets + @server.load_subnets + end + + def test_ms_provider_initialization + ::Proxy::DhcpPlugin.load_test_settings(:server => 'a_server') + assert_equal 'a_server', Proxy::DHCP::NativeMS::Provider.new.name end def test_should_load_subnets @@ -165,20 +170,20 @@ def test_should_load_subnets def test_subnet_should_have_options subnet = @server.find_subnet "172.29.205.0" - @server.loadSubnetOptions subnet + @server.load_subnet_options subnet assert subnet.options.size > 0 end def test_subnet_should_have_options_and_values subnet = @server.find_subnet "172.29.205.0" - @server.loadSubnetOptions subnet + @server.load_subnet_options subnet assert !subnet.options.any? { |o,v| o.to_s.empty? || v.nil? || v.to_s.empty? } end def test_records_should_have_options - @server.loadSubnetData(@server.find_subnet("172.29.205.0")) + @server.load_subnet_data(@server.find_subnet("172.29.205.0")) record = @subnet_service.all_leases("172.29.205.0").first @server.loadRecordOptions record @@ -186,7 +191,7 @@ def test_records_should_have_options end def test_records_should_have_options_and_values - @server.loadSubnetData(@server.find_subnet("172.29.205.0")) + @server.load_subnet_data(@server.find_subnet("172.29.205.0")) record = @subnet_service.all_leases("172.29.205.0").first @server.loadRecordOptions record diff --git a/test/dhcp_virsh/dhcp_virsh_provider_interface_test.rb b/test/dhcp_virsh/dhcp_virsh_provider_interface_test.rb new file mode 100644 index 000000000..202186880 --- /dev/null +++ b/test/dhcp_virsh/dhcp_virsh_provider_interface_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' +require 'dhcp_virsh/dhcp_virsh_main' + +class VirshDhcpProviderInterfaceTest < Test::Unit::TestCase + def test_provider_interface + assert_dhcp_provider_interface(::Proxy::DHCP::Virsh::Provider) + end +end diff --git a/test/dhcp_virsh/virsh_provider_test.rb b/test/dhcp_virsh/virsh_provider_test.rb new file mode 100644 index 000000000..4d0f17041 --- /dev/null +++ b/test/dhcp_virsh/virsh_provider_test.rb @@ -0,0 +1,79 @@ +require 'test_helper' +require 'dhcp_virsh/dhcp_virsh' +require 'dhcp_virsh/dhcp_virsh_main' +require 'dhcp_common/dependency_injection/dependencies' + +class VirshProviderTest < Test::Unit::TestCase + def setup + @service = Proxy::DHCP::SubnetService.new + @subnet_store = @service.subnets = Proxy::MemoryStore.new + @server = ::Proxy::DHCP::Virsh::Provider.new.initialize_for_testing(:virsh_network => 'default', + :name => "127.0.0.1", :service => @service) + @dump_xml = < + default + 25703051-f5d4-4a31-80b7-37bbbc4d19e1 + + + + + + + + + + + + + + + +EODUMPXML + end + + class DhcpVirshProviderForTesting < Proxy::DHCP::Virsh::Provider + attr_reader :network + end + + def test_virsh_provider_initialization + Proxy::SETTINGS.stubs(:virsh_network).returns('another_one') + assert_equal 'another_one', DhcpVirshProviderForTesting.new.network + end + + def test_should_load_subnets + @server.expects(:dump_xml).returns(@dump_xml) + @server.load_subnets + + assert @service.find_subnet("192.168.122.0") + assert_equal 1, @service.all_subnets.size + end + + def test_should_load_subnet_data + @server.expects(:dump_xml).returns(@dump_xml) + @server.load_subnet_data(Proxy::DHCP::Subnet.new("192.168.122.0", "255.255.255.0")) + + assert @service.find_host_by_ip("192.168.122.0", "192.168.122.10") + assert @service.find_host_by_ip("192.168.122.0", "192.168.122.11") + assert_equal 2, @service.all_hosts.size + end + + def test_should_add_record + to_add = { "hostname" => "test.example.com", "ip" => "192.168.122.10", + "mac" => "00:11:bb:cc:dd:ee", "network" => "192.168.122.0/255.255.255.0" } + + @service.add_subnet(Proxy::DHCP::Subnet.new("192.168.122.0", "255.255.255.0")) + @server.expects(:virsh_update_dhcp).with('add-last', to_add['mac'], to_add['ip'], to_add['hostname']) + @server.add_record(to_add) + end + + def test_should_remove_record + subnet = Proxy::DHCP::Subnet.new("192.168.122.0", "255.255.255.0") + @service.add_subnet(subnet) + to_delete = Proxy::DHCP::Reservation.new(:name => "test.example.com", :ip => "192.168.122.10", + :mac => "00:11:bb:cc:dd:ee", :subnet => subnet) + @service.add_host("192.168.122.0", to_delete) + @server.expects(:virsh_update_dhcp).with('delete', to_delete.mac, to_delete.ip, to_delete.hostname) + + @server.del_record("192.168.122.0", to_delete) + end +end \ No newline at end of file diff --git a/test/migrations/dhcp_migration_test.rb b/test/migrations/dhcp_migration_test.rb new file mode 100644 index 000000000..f234182e6 --- /dev/null +++ b/test/migrations/dhcp_migration_test.rb @@ -0,0 +1,71 @@ +require 'test_helper' + +require File.join(File.dirname(__FILE__),'../../extra/migrate_settings') +::Proxy::Migration.inject_migrations_instance(::Proxy::Migrations.new("dummy")) +require File.join(File.dirname(__FILE__),'../../extra/migrations/20150826000000_migrate_dhcp_settings') + +class ProxyDhcpMigrationTest < Test::Unit::TestCase + def setup + @migration = MigrateDhcpSettings.new("/tmp") + end + + def test_old_to_new_provider_name_conversion + assert_equal 'dhcp_isc', @migration.old_provider_name_to_new('isc') + assert_equal 'dhcp_native_ms', @migration.old_provider_name_to_new('native_ms') + assert_equal 'dhcp_virsh', @migration.old_provider_name_to_new('virsh') + assert_equal 'unknown', @migration.old_provider_name_to_new('unknown') + end + + def test_dhcp_parameter_remapping + assert_equal [:dhcp, :enabled, true], @migration.remap_parameter(:enabled, true) + assert_equal [:dhcp, :use_provider, 'dhcp_isc'], @migration.remap_parameter(:dhcp_vendor, 'isc') + assert_equal [:dhcp, :subnets, ['192.168.205.0/255.255.255.128']], + @migration.remap_parameter(:dhcp_subnets, ['192.168.205.0/255.255.255.128']) + assert_equal [:dhcp, :server, 'test.nowhere'], @migration.remap_parameter(:dhcp_server, 'test.nowhere') + end + + def test_dhcp_isc_parameter_mapping + assert_equal [:dhcp_isc, :config, 'some/path'], @migration.remap_parameter(:dhcp_config, 'some/path') + assert_equal [:dhcp_isc, :leases, 'some/path'], @migration.remap_parameter(:dhcp_leases, 'some/path') + assert_equal [:dhcp_isc, :key_name, 'key_name'], @migration.remap_parameter(:dhcp_key_name, 'key_name') + assert_equal [:dhcp_isc, :key_secret, 'key_secret'], @migration.remap_parameter(:dhcp_key_secret, 'key_secret') + assert_equal [:dhcp_isc, :omapi_port, '12345'], @migration.remap_parameter(:dhcp_omapi_port, '12345') + end + + def test_test_dhcp_parameter_remapping_of_unknown_parameter + assert_equal [:unknown, :a_parameter, 'avalue'], @migration.remap_parameter(:a_parameter, 'avalue') + end + + def test_migrate_dhcp_configuration + results = + @migration.migrate_dhcp_configuration(:enabled => true, + :dhcp_config => 'config/path', :dhcp_server => 'localhost') + + assert_equal true, results[:dhcp][:enabled] + assert_equal 'config/path', results[:dhcp_isc][:config] + assert_equal 'localhost', results[:dhcp][:server] + assert_equal 2, results.size + end + + def test_write_results_to_files + Dir.mktmpdir do |tmp_dir| + migration = MigrateDhcpSettings.new(tmp_dir) + migration.create_migration_dirs + + migration.write_to_files( + :dhcp => {:enabled => true}, + :dhcp_isc => {:config => 'some/path'}, + :unknown => {:parameter => 'value'}) + + assert File.exist?(dhcp_config_path = File.join(migration.dst_dir, 'settings.d', 'dhcp.yml')) + dhcp_contents = File.read(dhcp_config_path) + + assert dhcp_contents.include?(':enabled: true') + assert dhcp_contents.include?(':parameter: value') + + assert File.exist?(isc_config_path = File.join(migration.dst_dir, 'settings.d', 'dhcp_isc.yml')) + isc_contents = File.read(isc_config_path) + assert isc_contents.include?(':config: some/path') + end + end +end diff --git a/test/provider_interface_validation/dhcp_provider.rb b/test/provider_interface_validation/dhcp_provider.rb new file mode 100644 index 000000000..d40464234 --- /dev/null +++ b/test/provider_interface_validation/dhcp_provider.rb @@ -0,0 +1,14 @@ +module DhcpProviderInterfaceValidation + def assert_dhcp_provider_interface(dhcp_provider_class) + dhcp_provider = dhcp_provider_class.new + assert dhcp_provider.respond_to?(:load_subnets) + assert dhcp_provider.respond_to?(:load_subnet_data) + assert dhcp_provider.respond_to?(:find_subnet) + assert dhcp_provider.respond_to?(:subnets) + assert dhcp_provider.respond_to?(:all_hosts) + assert dhcp_provider.respond_to?(:unused_ip) + assert dhcp_provider.respond_to?(:find_record) + assert dhcp_provider.respond_to?(:add_record) + assert dhcp_provider.respond_to?(:del_record) + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index be1591791..6e673bd9d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -16,3 +16,6 @@ require "rack/test" require 'smart_proxy_for_testing' +require 'provider_interface_validation/dhcp_provider' + +include DhcpProviderInterfaceValidation