Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ipv6 combined #10400

Draft
wants to merge 6 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion app/controllers/unattended_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,28 @@ def set_content_type
# the form save)
def update_ip
ip = request.remote_ip
logger.debug "Built notice from #{ip}, current host ip is #{@host.ip}, updating" if @host.ip != ip
address = IPAddr.new(ip)

# @host has been changed even if the save fails, so we have to change it back
if address.ipv4?
update_ip4(ip)
elsif address.ipv6? && !address.link_local?
update_ip6(ip)
end
end

def update_ip4(ip)
logger.debug "Built notice from #{ip}, current host ip is #{@host.ip}, updating" if @host.ip != ip
old_ip = @host.ip
@host.ip = old_ip unless @host.update({'ip' => ip})
end

def update_ip6(ip)
logger.debug "Built notice from #{ip}, current host ip is #{@host.ip6}, updating" if @host.ip6 != ip
old_ip = @host.ip6
@host.ip6 = old_ip unless @host.update({'ip6' => ip})
end

def ipxe_request?
%w[iPXE gPXE].include?(params[:kind])
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/concerns/orchestration/ssh_provision.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,6 @@ def validate_ssh_provisioning

def provision_host
# usually cloud compute resources provide IPs but virtualization do not
provision_interface.ip || provision_interface.fqdn
provision_interface.ip6 || provision_interface.ip || provision_interface.fqdn
end
end
29 changes: 15 additions & 14 deletions app/services/fact_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def interfaces
# tries to detect primary interface among interfaces using host name
def suggested_primary_interface(host)
# we search among interface with ip and mac if we didn't find it by name
potential = interfaces.select { |_, values| values[:ipaddress].present? && values[:macaddress].present? }
potential = interfaces.select { |_, values| (values[:ipaddress].present? || values[:ipaddress6].present?) && values[:macaddress].present? }
find_interface_by_name(host.name) || find_physical_interface(potential) ||
find_virtual_interface(potential) || potential.first || interfaces.first
end
Expand Down Expand Up @@ -139,20 +139,21 @@ def bios
def find_interface_by_name(host_name)
resolver = Resolv::DNS.new
resolver.timeouts = PRIMARY_INTERFACE_RESOLVE_TIMEOUTS
interfaces.detect do |int, values|
if (ip = values[:ipaddress]).present?
begin
logger.debug { "Resolving fact '#{ip}' via DNS to match reported hostname #{host_name}" }
if resolver.getnames(ip).any? { |name| name.to_s == host_name }
logger.debug { "Match: '#{ip}', interface #{int} is selected as primary" }
return [int, values]
end
rescue Resolv::ResolvError => e
logger.debug { "Could not resolv name for #{ip} because of #{e} #{e.message}" }
nil
end
end
interfaces.find do |int, values|
[values[:ipaddress], values[:ipaddress6]].find { |ip| try_resolve(int, ip, host_name, resolver) }
end
end

def try_resolve(int, ip, host_name, resolver)
return nil unless ip
logger.debug { "Resolving fact '#{ip}' via DNS to match reported hostname #{host_name}" }
if resolver.getnames(ip).any? { |name| name.to_s == host_name }
logger.debug { "Match: '#{ip}', interface #{int} is selected as primary" }
true
end
rescue Resolv::ResolvError => e
logger.debug { "Could not resolv name for #{ip} because of #{e} #{e.message}" }
nil
end

def find_physical_interface(interfaces)
Expand Down
10 changes: 8 additions & 2 deletions app/services/foreman/unattended_installation/host_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,14 @@ def find_host_by_ip_or_mac

mac_list = query_params[:mac_list]

query = mac_list.empty? ? { :nics => { :ip => ip } } : ["lower(nics.mac) IN (?)", mac_list]
hosts = Host.joins(:provision_interface).where(query).order(:created_at)
# create base query
hosts = Host.joins(:provision_interface).order(:created_at)
# filter the records either by mac or by the IP address
if mac_list.empty?
hosts = hosts.where(:nics => { :ip => ip }).or(hosts.where(:nics => { :ip6 => ip }))
else
hosts = hosts.where("lower(nics.mac) IN (?)", mac_list)
end

Rails.logger.warn("Multiple hosts found with #{ip} or #{mac_list}, picking up the most recent") if hosts.count > 1

Expand Down
25 changes: 25 additions & 0 deletions test/controllers/unattended_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ class UnattendedControllerTest < ActionController::TestCase
assert_response :success
end

test "should get a kickstart when pure IPv6 address is used" do
ptable = Ptable.find_by(name: 'default')
@ipv6_host = FactoryBot.create(:host, :managed, :with_ipv6, :build => true,
:operatingsystem => operatingsystems(:redhat),
:ptable => ptable,
:medium => media(:one),
:architecture => architectures(:x86_64),
:organization => @org,
:location => @loc)
@request.env["HTTP_X_FORWARDED_FOR"] = @ipv6_host.ip6
@request.env["REMOTE_ADDR"] = "::1"
get :host_template, params: { :kind => 'provision' }
assert_response :success
end

test "should set @static when requested" do
Setting[:safemode_render] = false
@request.env["HTTP_X_RHN_PROVISIONING_MAC_0"] = "eth0 #{@rh_host.mac}"
Expand Down Expand Up @@ -373,6 +388,16 @@ class UnattendedControllerTest < ActionController::TestCase
refute nic.build
end

test "should accept built notifications over ipv6 and store the address" do
nic6 = '2001:db8::1234'
Setting[:update_ip_from_built_request] = true
@request.env["REMOTE_ADDR"] = nic6
post :built, params: { mac: @ub_host.primary_interface.mac }
assert_response :created
@ub_host.reload
assert_equal nic6, @ub_host.primary_interface.ip6
end

test "should accept failed notifications" do
@request.env["REMOTE_ADDR"] = @ub_host.ip
post :failed
Expand Down
32 changes: 32 additions & 0 deletions test/unit/fact_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,22 @@ class FactParserTest < ActiveSupport::TestCase
assert_equal '00:00:00:00:00:12', found.last[:macaddress]
end

test "#suggested_primary_interface detects primary interface using DNS IPv6" do
parser.stubs(:interfaces).returns({
'br0' => {'ipaddress6' => '2001:db8::10', 'macaddress' => '00:00:00:00:00:10'},
'em1' => {'ipaddress6' => '2001:db8::20', 'macaddress' => '00:00:00:00:00:20'},
'em2' => {'ipaddress6' => '2001:db8::30', 'macaddress' => '00:00:00:00:00:30'},
'bond0' => {'ipaddress6' => '2001:db8::40', 'macaddress' => '00:00:00:00:00:40'},
}.with_indifferent_access)

Resolv::DNS.any_instance.stubs(:getnames).returns([])
Resolv::DNS.any_instance.expects(:getnames).with('2001:db8::30').returns([host.name])
found = parser.suggested_primary_interface(host)
assert_equal 'em2', found.first
assert_equal '2001:db8::30', found.last[:ipaddress6]
assert_equal '00:00:00:00:00:30', found.last[:macaddress]
end

test "#suggested_primary_interface primary interface detection falls back to physical with ip and mac" do
parser.stubs(:interfaces).returns({
'br0' => {'ipaddress' => '30.0.0.30', 'macaddress' => '00:00:00:00:00:30'},
Expand All @@ -313,6 +329,22 @@ class FactParserTest < ActiveSupport::TestCase
assert_equal '00:00:00:00:00:10', found.last[:macaddress]
end

test "#suggested_primary_interface primary interface detection falls back to physical with ip and mac" do
parser.stubs(:interfaces).returns({
'br0' => {'ipaddress6' => '2001:db8::10', 'macaddress' => '00:00:00:00:00:30'},
'em0' => {'ipaddress6' => '', 'macaddress' => ''},
'em1' => {'ipaddress6' => '2001:db8::20', 'macaddress' => '00:00:00:00:00:10'},
'em2' => {'ipaddress6' => '2001:db8::30', 'macaddress' => '00:00:00:00:00:12'},
'bond0' => {'ipaddress6' => '2001:db8::40', 'macaddress' => '00:00:00:00:00:15'},
}.with_indifferent_access)

Resolv::DNS.any_instance.stubs(:getnames).returns([])
found = parser.suggested_primary_interface(host)
assert_equal 'em1', found.first
assert_equal '2001:db8::20', found.last[:ipaddress6]
assert_equal '00:00:00:00:00:10', found.last[:macaddress]
end

test "#suggested_primary_interface primary interface detection falls back to first with ip and mac if no physical" do
parser.stubs(:interfaces).returns({
'bond1' => {'ipaddress' => '', 'macaddress' => ''},
Expand Down
Loading