summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThom May <thom@may.lt>2015-08-14 15:07:56 +0100
committerThom May <thom@may.lt>2015-08-14 15:07:56 +0100
commit1954313314a1b9290eb69a4270355ec6a1ef5ebc (patch)
tree46ab2002ccdbf41a72b001f7c5b167f32828c81f
parentd756a5d3d93ca3e6e2e9840bab7e633f55d17b10 (diff)
parentba5f240b717d11e34c1d0dfbd0e4b1807ecfb968 (diff)
downloadohai-1954313314a1b9290eb69a4270355ec6a1ef5ebc.tar.gz
Merge pull request #547 from MichaelSp/master
return correct ipaddress for openvz guests (fixes #415)
-rw-r--r--lib/ohai/plugins/linux/network.rb435
-rw-r--r--spec/unit/plugins/linux/network_spec.rb40
2 files changed, 285 insertions, 190 deletions
diff --git a/lib/ohai/plugins/linux/network.rb b/lib/ohai/plugins/linux/network.rb
index 50360bb6..51eff9dd 100644
--- a/lib/ohai/plugins/linux/network.rb
+++ b/lib/ohai/plugins/linux/network.rb
@@ -41,6 +41,246 @@ Ohai.plugin(:Network) do
["/sbin/ip", "/usr/bin/ip", "/bin/ip"].any? { |path| File.exist?(path) }
end
+ def is_openvz?
+ ::File.directory?('/proc/vz')
+ end
+
+ def is_openvz_host?
+ is_openvz? && ::File.directory?('/proc/bc')
+ end
+
+ def extract_neighbors(family, iface, neigh_attr)
+ so = shell_out("ip -f #{family[:name]} neigh show")
+ so.stdout.lines do |line|
+ if line =~ /^([a-f0-9\:\.]+)\s+dev\s+([^\s]+)\s+lladdr\s+([a-fA-F0-9\:]+)/
+ interface = iface[$2]
+ unless interface
+ Ohai::Log.warn("neighbor list has entries for unknown interface #{interface}")
+ next
+ end
+ interface[neigh_attr] = Mash.new unless interface[neigh_attr]
+ interface[neigh_attr][$1] = $3.downcase
+ end
+ end
+ iface
+ end
+
+ # checking the routing tables
+ # why ?
+ # 1) to set the default gateway and default interfaces attributes
+ # 2) on some occasions, the best way to select node[:ipaddress] is to look at
+ # the routing table source field.
+ # 3) and since we're at it, let's populate some :routes attributes
+ # (going to do that for both inet and inet6 addresses)
+ def check_routing_table(family, iface)
+ so = shell_out("ip -o -f #{family[:name]} route show")
+ so.stdout.lines do |line|
+ line.strip!
+ Ohai::Log.debug("Parsing #{line}")
+ if line =~ /\\/
+ parts = line.split('\\')
+ route_dest = parts.shift.strip
+ route_endings = parts
+ elsif line =~ /^([^\s]+)\s(.*)$/
+ route_dest = $1
+ route_endings = [$2]
+ else
+ next
+ end
+ route_endings.each do |route_ending|
+ if route_ending =~ /\bdev\s+([^\s]+)\b/
+ route_int = $1
+ else
+ Ohai::Log.debug("Skipping route entry without a device: '#{line}'")
+ next
+ end
+ route_int = 'venet0:0' if is_openvz? && !is_openvz_host? && route_int == 'venet0' && iface['venet0:0']
+
+ unless iface[route_int]
+ Ohai::Log.debug("Skipping previously unseen interface from 'ip route show': #{route_int}")
+ next
+ end
+
+ route_entry = Mash.new(:destination => route_dest,
+ :family => family[:name])
+ %w[via scope metric proto src].each do |k|
+ route_entry[k] = $1 if route_ending =~ /\b#{k}\s+([^\s]+)\b/
+ end
+
+ # a sanity check, especially for Linux-VServer, OpenVZ and LXC:
+ # don't report the route entry if the src address isn't set on the node
+ next if route_entry[:src] and not iface[route_int][:addresses].has_key? route_entry[:src]
+
+ iface[route_int][:routes] = Array.new unless iface[route_int][:routes]
+ iface[route_int][:routes] << route_entry
+ end
+ end
+ iface
+ end
+
+ # now looking at the routes to set the default attributes
+ # for information, default routes can be of this form :
+ # - default via 10.0.2.4 dev br0
+ # - default dev br0 scope link
+ # - default via 10.0.3.1 dev eth1 src 10.0.3.2 metric 10
+ # - default via 10.0.4.1 dev eth2 src 10.0.4.2 metric 20
+
+ # using a temporary var to hold routes and their interface name
+ def parse_routes(family, iface)
+ iface.collect do |i, iv|
+ iv[:routes].collect do |r|
+ r.merge(:dev => i) if r[:family] == family[:name]
+ end.compact if iv[:routes]
+ end.compact.flatten
+ end
+
+
+ def link_statistics(iface, net_counters)
+ so = shell_out("ip -d -s link")
+ tmp_int = nil
+ on_rx = true
+ so.stdout.lines do |line|
+ if line =~ IPROUTE_INT_REGEX
+ tmp_int = $2
+ iface[tmp_int] = Mash.new unless iface[tmp_int]
+ net_counters[tmp_int] = Mash.new unless net_counters[tmp_int]
+ end
+
+ if line =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/
+ int = on_rx ? :rx : :tx
+ net_counters[tmp_int][int] = Mash.new unless net_counters[tmp_int][int]
+ net_counters[tmp_int][int][:bytes] = $1
+ net_counters[tmp_int][int][:packets] = $2
+ net_counters[tmp_int][int][:errors] = $3
+ net_counters[tmp_int][int][:drop] = $4
+ if (int == :rx)
+ net_counters[tmp_int][int][:overrun] = $5
+ else
+ net_counters[tmp_int][int][:carrier] = $5
+ net_counters[tmp_int][int][:collisions] = $6
+ end
+
+ on_rx = !on_rx
+ end
+
+ if line =~ /qlen (\d+)/
+ net_counters[tmp_int][:tx] = Mash.new unless net_counters[tmp_int][:tx]
+ net_counters[tmp_int][:tx][:queuelen] = $1
+ end
+
+ if line =~ /vlan id (\d+)/ or line =~ /vlan protocol ([\w\.]+) id (\d+)/
+ if $2
+ tmp_prot = $1
+ tmp_id = $2
+ else
+ tmp_id = $1
+ end
+ iface[tmp_int][:vlan] = Mash.new unless iface[tmp_int][:vlan]
+ iface[tmp_int][:vlan][:id] = tmp_id
+ iface[tmp_int][:vlan][:protocol] = tmp_prot if tmp_prot
+
+ vlan_flags = line.scan(/(REORDER_HDR|GVRP|LOOSE_BINDING)/)
+ if vlan_flags.length > 0
+ iface[tmp_int][:vlan][:flags] = vlan_flags.flatten.uniq
+ end
+ end
+
+ if line =~ /state (\w+)/
+ iface[tmp_int]['state'] = $1.downcase
+ end
+ end
+ iface
+ end
+
+ def match_iproute(iface, line, cint)
+ if line =~ IPROUTE_INT_REGEX
+ cint = $2
+ iface[cint] = Mash.new
+ if cint =~ /^(\w+)(\d+.*)/
+ iface[cint][:type] = $1
+ iface[cint][:number] = $2
+ end
+
+ if line =~ /mtu (\d+)/
+ iface[cint][:mtu] = $1
+ end
+
+ flags = line.scan(/(UP|BROADCAST|DEBUG|LOOPBACK|POINTTOPOINT|NOTRAILERS|LOWER_UP|NOARP|PROMISC|ALLMULTI|SLAVE|MASTER|MULTICAST|DYNAMIC)/)
+ if flags.length > 1
+ iface[cint][:flags] = flags.flatten.uniq
+ end
+ end
+ cint
+ end
+
+ def parse_ip_addr(iface)
+ so = shell_out("ip addr")
+ cint = nil
+ so.stdout.lines do |line|
+ cint = match_iproute(iface, line, cint)
+
+ parse_ip_addr_link_line(cint, iface, line)
+ cint = parse_ip_addr_inet_line(cint, iface, line)
+ parse_ip_addr_inet6_line(cint, iface, line)
+ end
+ end
+
+
+ def parse_ip_addr_link_line(cint, iface, line)
+ if line =~ /link\/(\w+) ([\da-f\:]+) /
+ iface[cint][:encapsulation] = linux_encaps_lookup($1)
+ unless $2 == "00:00:00:00:00:00"
+ iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
+ iface[cint][:addresses][$2.upcase] = {"family" => "lladdr"}
+ end
+ end
+ end
+
+ def parse_ip_addr_inet_line(cint, iface, line)
+ if line =~ /inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/(\d{1,2}))?/
+ tmp_addr, tmp_prefix = $1, $3
+ tmp_prefix ||= "32"
+ original_int = nil
+
+ # Are we a formerly aliased interface?
+ if line =~ /#{cint}:(\d+)$/
+ sub_int = $1
+ alias_int = "#{cint}:#{sub_int}"
+ original_int = cint
+ cint = alias_int
+ end
+
+ iface[cint] = Mash.new unless iface[cint] # Create the fake alias interface if needed
+ iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
+ iface[cint][:addresses][tmp_addr] = {"family" => "inet", "prefixlen" => tmp_prefix}
+ iface[cint][:addresses][tmp_addr][:netmask] = IPAddr.new("255.255.255.255").mask(tmp_prefix.to_i).to_s
+
+ if line =~ /peer (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
+ iface[cint][:addresses][tmp_addr][:peer] = $1
+ end
+
+ if line =~ /brd (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
+ iface[cint][:addresses][tmp_addr][:broadcast] = $1
+ end
+
+ if line =~ /scope (\w+)/
+ iface[cint][:addresses][tmp_addr][:scope] = ($1.eql?("host") ? "Node" : $1.capitalize)
+ end
+
+ # If we found we were an an alias interface, restore cint to its original value
+ cint = original_int unless original_int.nil?
+ end
+ cint
+ end
+
+ def parse_ip_addr_inet6_line(cint, iface, line)
+ if line =~ /inet6 ([a-f0-9\:]+)\/(\d+) scope (\w+)/
+ iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
+ tmp_addr = $1
+ iface[cint][:addresses][tmp_addr] = {"family" => "inet6", "prefixlen" => $2, "scope" => ($3.eql?("host") ? "Node" : $3.capitalize)}
+ end
+ end
+
collect_data(:linux) do
require 'ipaddr'
@@ -73,204 +313,19 @@ Ohai.plugin(:Network) do
:neighbour_attribute => :neighbour_inet6
} if ipv6_enabled?
- so = shell_out("ip addr")
- cint = nil
- so.stdout.lines do |line|
- if line =~ IPROUTE_INT_REGEX
- cint = $2
- iface[cint] = Mash.new
- if cint =~ /^(\w+)(\d+.*)/
- iface[cint][:type] = $1
- iface[cint][:number] = $2
- end
-
- if line =~ /mtu (\d+)/
- iface[cint][:mtu] = $1
- end
-
- flags = line.scan(/(UP|BROADCAST|DEBUG|LOOPBACK|POINTTOPOINT|NOTRAILERS|LOWER_UP|NOARP|PROMISC|ALLMULTI|SLAVE|MASTER|MULTICAST|DYNAMIC)/)
- if flags.length > 1
- iface[cint][:flags] = flags.flatten.uniq
- end
- end
- if line =~ /link\/(\w+) ([\da-f\:]+) /
- iface[cint][:encapsulation] = linux_encaps_lookup($1)
- unless $2 == "00:00:00:00:00:00"
- iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
- iface[cint][:addresses][$2.upcase] = { "family" => "lladdr" }
- end
- end
- if line =~ /inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/(\d{1,2}))?/
- tmp_addr, tmp_prefix = $1, $3
- tmp_prefix ||= "32"
- original_int = nil
-
- # Are we a formerly aliased interface?
- if line =~ /#{cint}:(\d+)$/
- sub_int = $1
- alias_int = "#{cint}:#{sub_int}"
- original_int = cint
- cint = alias_int
- end
+ parse_ip_addr(iface)
- iface[cint] = Mash.new unless iface[cint] # Create the fake alias interface if needed
- iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
- iface[cint][:addresses][tmp_addr] = { "family" => "inet", "prefixlen" => tmp_prefix }
- iface[cint][:addresses][tmp_addr][:netmask] = IPAddr.new("255.255.255.255").mask(tmp_prefix.to_i).to_s
-
- if line =~ /peer (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
- iface[cint][:addresses][tmp_addr][:peer] = $1
- end
-
- if line =~ /brd (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
- iface[cint][:addresses][tmp_addr][:broadcast] = $1
- end
-
- if line =~ /scope (\w+)/
- iface[cint][:addresses][tmp_addr][:scope] = ($1.eql?("host") ? "Node" : $1.capitalize)
- end
-
- # If we found we were an an alias interface, restore cint to its original value
- cint = original_int unless original_int.nil?
- end
- if line =~ /inet6 ([a-f0-9\:]+)\/(\d+) scope (\w+)/
- iface[cint][:addresses] = Mash.new unless iface[cint][:addresses]
- tmp_addr = $1
- iface[cint][:addresses][tmp_addr] = { "family" => "inet6", "prefixlen" => $2, "scope" => ($3.eql?("host") ? "Node" : $3.capitalize) }
- end
- end
-
- so = shell_out("ip -d -s link")
- tmp_int = nil
- on_rx = true
- so.stdout.lines do |line|
- if line =~ IPROUTE_INT_REGEX
- tmp_int = $2
- iface[tmp_int] = Mash.new unless iface[tmp_int]
- net_counters[tmp_int] = Mash.new unless net_counters[tmp_int]
- end
-
- if line =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/
- int = on_rx ? :rx : :tx
- net_counters[tmp_int][int] = Mash.new unless net_counters[tmp_int][int]
- net_counters[tmp_int][int][:bytes] = $1
- net_counters[tmp_int][int][:packets] = $2
- net_counters[tmp_int][int][:errors] = $3
- net_counters[tmp_int][int][:drop] = $4
- if(int == :rx)
- net_counters[tmp_int][int][:overrun] = $5
- else
- net_counters[tmp_int][int][:carrier] = $5
- net_counters[tmp_int][int][:collisions] = $6
- end
-
- on_rx = !on_rx
- end
-
- if line =~ /qlen (\d+)/
- net_counters[tmp_int][:tx] = Mash.new unless net_counters[tmp_int][:tx]
- net_counters[tmp_int][:tx][:queuelen] = $1
- end
-
- if line =~ /vlan id (\d+)/ or line =~ /vlan protocol ([\w\.]+) id (\d+)/
- if $2
- tmp_prot = $1
- tmp_id = $2
- else
- tmp_id = $1
- end
- iface[tmp_int][:vlan] = Mash.new unless iface[tmp_int][:vlan]
- iface[tmp_int][:vlan][:id] = tmp_id
- iface[tmp_int][:vlan][:protocol] = tmp_prot if tmp_prot
-
- vlan_flags = line.scan(/(REORDER_HDR|GVRP|LOOSE_BINDING)/)
- if vlan_flags.length > 0
- iface[tmp_int][:vlan][:flags] = vlan_flags.flatten.uniq
- end
- end
-
- if line =~ /state (\w+)/
- iface[tmp_int]['state'] = $1.downcase
- end
- end
+ iface = link_statistics(iface, net_counters)
families.each do |family|
neigh_attr = family[:neighbour_attribute]
default_prefix = family[:default_prefix]
- so = shell_out("ip -f #{family[:name]} neigh show")
- so.stdout.lines do |line|
- if line =~ /^([a-f0-9\:\.]+)\s+dev\s+([^\s]+)\s+lladdr\s+([a-fA-F0-9\:]+)/
- unless iface[$2]
- Ohai::Log.warn("neighbour list has entries for unknown interface #{iface[$2]}")
- next
- end
- iface[$2][neigh_attr] = Mash.new unless iface[$2][neigh_attr]
- iface[$2][neigh_attr][$1] = $3.downcase
- end
- end
-
- # checking the routing tables
- # why ?
- # 1) to set the default gateway and default interfaces attributes
- # 2) on some occasions, the best way to select node[:ipaddress] is to look at
- # the routing table source field.
- # 3) and since we're at it, let's populate some :routes attributes
- # (going to do that for both inet and inet6 addresses)
- so = shell_out("ip -o -f #{family[:name]} route show")
- so.stdout.lines do |line|
- line.strip!
- Ohai::Log.debug("Parsing #{line}")
- if line =~ /\\/
- parts = line.split('\\')
- route_dest = parts.shift.strip
- route_endings = parts
- elsif line =~ /^([^\s]+)\s(.*)$/
- route_dest = $1
- route_endings = [$2]
- else
- next
- end
- route_endings.each do |route_ending|
- if route_ending =~ /\bdev\s+([^\s]+)\b/
- route_int = $1
- else
- Ohai::Log.debug("Skipping route entry without a device: '#{line}'")
- next
- end
-
- unless iface[route_int]
- Ohai::Log.debug("Skipping previously unseen interface from 'ip route show': #{route_int}")
- next
- end
-
- route_entry = Mash.new( :destination => route_dest,
- :family => family[:name] )
- %w[via scope metric proto src].each do |k|
- route_entry[k] = $1 if route_ending =~ /\b#{k}\s+([^\s]+)\b/
- end
+ iface = extract_neighbors(family, iface, neigh_attr)
- # a sanity check, especially for Linux-VServer, OpenVZ and LXC:
- # don't report the route entry if the src address isn't set on the node
- next if route_entry[:src] and not iface[route_int][:addresses].has_key? route_entry[:src]
+ iface = check_routing_table(family, iface)
- iface[route_int][:routes] = Array.new unless iface[route_int][:routes]
- iface[route_int][:routes] << route_entry
- end
- end
- # now looking at the routes to set the default attributes
- # for information, default routes can be of this form :
- # - default via 10.0.2.4 dev br0
- # - default dev br0 scope link
- # - default via 10.0.3.1 dev eth1 src 10.0.3.2 metric 10
- # - default via 10.0.4.1 dev eth2 src 10.0.4.2 metric 20
-
- # using a temporary var to hold routes and their interface name
- routes = iface.collect do |i,iv|
- iv[:routes].collect do |r|
- r.merge(:dev=>i) if r[:family] == family[:name]
- end.compact if iv[:routes]
- end.compact.flatten
+ routes = parse_routes(family, iface)
# using a temporary var to hold the default route
# in case there are more than 1 default route, sort it by its metric
diff --git a/spec/unit/plugins/linux/network_spec.rb b/spec/unit/plugins/linux/network_spec.rb
index 696a874e..7b5dcbde 100644
--- a/spec/unit/plugins/linux/network_spec.rb
+++ b/spec/unit/plugins/linux/network_spec.rb
@@ -888,6 +888,46 @@ fe80::/64 dev eth0.11 proto kernel metric 256
end
end
+ describe "with openvz setup" do
+ let(:linux_ip_route) {'default dev venet0 scope link' }
+ let(:linux_ip_addr) {'1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+ inet 127.0.0.1/8 scope host lo
+ inet6 ::1/128 scope host
+ valid_lft forever preferred_lft forever
+2: venet0: <BROADCAST,POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
+ link/void
+ inet 127.0.0.2/32 scope host venet0
+ inet 10.116.201.76/24 brd 10.116.201.255 scope global venet0:0
+ inet6 2001:44b8:4160:8f00:a00:27ff:fe13:eacd/64 scope global dynamic
+ valid_lft 6128sec preferred_lft 2526sec
+'}
+
+ before(:each) do
+ allow(plugin).to receive(:is_openvz?).and_return true
+ allow(plugin).to receive(:is_openvz_host?).and_return false
+ plugin.run
+ end
+
+ it "completes the run" do
+ expect(Ohai::Log).not_to receive(:debug).with(/Plugin linux::network threw exception/)
+ expect(plugin['network']).not_to be_nil
+ end
+
+ it "sets default ipv4 interface and gateway" do
+ expect(plugin['network']['default_interface']).to eq('venet0:0')
+ expect(plugin['network']['default_gateway']).to eq('0.0.0.0')
+ end
+
+ it "sets correct routing information" do
+ expect(plugin['network']['interfaces']['venet0:0']['routes']).to eq([Mash.new( :destination => "default", :family => "inet", :scope => "link" )])
+ end
+
+ it "sets correct address information" do
+ expect(plugin['network']['interfaces']['venet0:0']['addresses']).to eq("10.116.201.76" => Mash.new(:family => 'inet', :prefixlen => '24', :netmask =>'255.255.255.0', :broadcast => '10.116.201.255', :scope => "Global"))
+ end
+ end
+
describe "with irrelevant routes (container setups)" do
let(:linux_ip_route) {
'10.116.201.0/26 dev eth0 proto kernel src 10.116.201.39