313 lines
9.0 KiB
Ruby
313 lines
9.0 KiB
Ruby
require_relative '../loader/l23network'
|
|
require 'yaml'
|
|
require 'json'
|
|
|
|
class Puppet::Provider::InterfaceToolset < Puppet::Provider
|
|
|
|
def self.iproute(*cmd)
|
|
actual_cmd = ['ip'] + Array(*cmd)
|
|
debug("iproute: run the command #{actual_cmd.join(' ')}")
|
|
rv = []
|
|
IO.popen(actual_cmd.join(' ') + ' 2>&1') do |ff|
|
|
rv = ff.readlines().map{|l| l.chomp()}
|
|
ff.close
|
|
if 0 != $?.exitstatus
|
|
raise Puppet::ExecutionFailure, "Command '#{actual_cmd.join(' ')}' has been failed with exit_code=#{$?.exitstatus}:\n#{rv.join("\n")}"
|
|
end
|
|
end
|
|
return rv
|
|
end
|
|
|
|
def self.ovs_vsctl(*cmd)
|
|
actual_cmd = ['ovs-vsctl'] + Array(*cmd)
|
|
rv = []
|
|
IO.popen(actual_cmd.join(' ') + ' 2>&1') do |ff|
|
|
rv = ff.readlines().map{|l| l.chomp()}
|
|
ff.close
|
|
if 0 != $?.exitstatus
|
|
rv = nil
|
|
end
|
|
end
|
|
return rv
|
|
end
|
|
|
|
def self.brctl(*cmd)
|
|
actual_cmd = ['brctl'] + Array(*cmd)
|
|
rv = []
|
|
IO.popen(actual_cmd.join(' ') + ' 2>&1') do |ff|
|
|
rv = ff.readlines().map{|l| l.chomp()}
|
|
ff.close
|
|
if 0 != $?.exitstatus
|
|
rv = nil
|
|
end
|
|
end
|
|
return rv
|
|
end
|
|
|
|
def self.ethtool(*cmd)
|
|
actual_cmd = ['ethtool'] + Array(*cmd)
|
|
rv = []
|
|
debug(actual_cmd.join(' ') + ' 2>&1')
|
|
IO.popen(actual_cmd.join(' ') + ' 2>&1') do |ff|
|
|
rv = ff.readlines().map{|l| l.chomp()}
|
|
ff.close
|
|
if 0 != $?.exitstatus and 94 != $?.exitstatus # 94 is a 'no stats available'. it's a not error.
|
|
rv = nil
|
|
end
|
|
end
|
|
return rv
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def self.iface_exist?(iface)
|
|
File.exist? "/sys/class/net/#{iface}"
|
|
end
|
|
|
|
def self.set_mtu(iface, mtu=1500)
|
|
if File.symlink?("/sys/class/net/#{iface}")
|
|
debug("Set MTU to '#{mtu}' for interface '#{iface}'")
|
|
set_sys_class("/sys/class/net/#{iface}/mtu", mtu)
|
|
end
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def self.set_sys_class(property, value)
|
|
debug("SET sys.property: #{property} << #{value}")
|
|
begin
|
|
property_file = File.open(property, 'a')
|
|
property_file.write("#{value.to_s}")
|
|
property_file.close
|
|
rv = true
|
|
rescue Exception => e
|
|
debug("Non-fatal-Error: Can't set property '#{property}' to '#{value}': #{e.message}")
|
|
rv = false
|
|
end
|
|
return rv
|
|
end
|
|
|
|
def self.get_sys_class(property, array=false)
|
|
as_array = (array ? ' as array' : '')
|
|
debug("GET sys.property: #{property}#{as_array}.")
|
|
begin
|
|
rv = File.open(property).read.split(/\s+/)
|
|
rescue Exception => e
|
|
debug("Non-fatal-Error: Can't get property '#{property}': #{e.message}")
|
|
rv = ['']
|
|
end
|
|
(array ? rv : rv[0])
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def self.get_iface_peer_index(iface)
|
|
# returns:
|
|
# N -- ifindex of peer interface
|
|
# nil -- interface in not an veth pair jack
|
|
cmd = ['-S', iface]
|
|
begin
|
|
rv = ethtool(cmd)
|
|
rescue Exception => e
|
|
debug("Non-fatal-Error: Can't run ethtool for interface '#{iface}': #{e.message}")
|
|
return nil
|
|
end
|
|
rv = rv.select{|l| l =~ /\s*peer_ifindex\:\s+\d+/}[0]
|
|
(rv.nil? ? nil : rv.split(':')[-1].strip.to_i)
|
|
end
|
|
|
|
def self.get_iface_state(iface)
|
|
# returns:
|
|
# true -- interface in UP state
|
|
# false -- interface in UP state, but no-carrier
|
|
# nil -- interface in DOWN state
|
|
begin
|
|
1 == File.open("/sys/class/net/#{iface}/carrier").read.chomp.to_i
|
|
rescue
|
|
# if interface is down, this file can't be read
|
|
nil
|
|
end
|
|
end
|
|
|
|
def self.interface_up(iface, force=false)
|
|
debug("Setting #{iface} up")
|
|
cmd = ['link', 'set', 'up', 'dev', iface]
|
|
cmd.insert(0, '--force') if force
|
|
begin
|
|
iproute(cmd)
|
|
rv = true
|
|
rescue Exception => e
|
|
debug("Non-fatal-Error: Can't put interface '#{iface}' to UP state: #{e.message}")
|
|
rv = false
|
|
end
|
|
return rv
|
|
end
|
|
|
|
def self.interface_down(iface, force=false)
|
|
debug("Setting #{iface} down")
|
|
cmd = ['link', 'set', 'down', 'dev', iface]
|
|
cmd.insert(0, '--force') if force
|
|
begin
|
|
iproute(cmd)
|
|
rv = true
|
|
rescue Exception => e
|
|
debug("Non-fatal-Error: Can't put interface '#{iface}' to DOWN state: #{e.message}")
|
|
rv = false
|
|
end
|
|
return rv
|
|
end
|
|
# ---------------------------------------------------------------------------
|
|
def self.ipaddr_exist?(if_name)
|
|
rv = false
|
|
iproute(['-o', 'addr', 'show', 'dev', if_name]).map{|l| l.split(/\s+/)}.each do |line|
|
|
if line[2].match(/^inet\d?$/)
|
|
rv=true
|
|
break
|
|
end
|
|
end
|
|
return rv
|
|
end
|
|
|
|
def self.addr_flush(iface, force=false)
|
|
cmd = ['addr', 'flush', 'dev', iface]
|
|
cmd.insert(0, '--force') if force
|
|
begin
|
|
iproute(cmd)
|
|
rv = true
|
|
rescue Exception => e
|
|
debug("Non-fatal-Error: Can't flush addr for interface '#{iface}': #{e.message}")
|
|
rv = false
|
|
end
|
|
return rv
|
|
end
|
|
|
|
def self.route_flush(iface, force=false)
|
|
cmd = ['route', 'flush', 'dev', iface]
|
|
cmd.insert(0, '--force') if force
|
|
begin
|
|
iproute(cmd)
|
|
rv = true
|
|
rescue Exception => e
|
|
debug("Non-fatal-Error: Can't flush routes for interface '#{iface}': #{e.message}")
|
|
rv = false
|
|
end
|
|
return rv
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def self.ensure_upstart_state_file(iface, base_dir='/var/run/network')
|
|
if File.directory?(base_dir)
|
|
if_name = iface.to_s
|
|
# we run under Debian/Ubuntu ifup/ifdown compatible network initialize subsystem
|
|
if ! File.file?("#{base_dir}/ifstate.#{if_name}")
|
|
# create state file for network interface for upstert
|
|
f = File.new("#{base_dir}/ifstate.#{if_name}", mode="w")
|
|
f.puts(if_name)
|
|
f.close()
|
|
end
|
|
# add interfase to alias list
|
|
if ! File.file?("#{base_dir}/ifstate")
|
|
f = File.new("#{base_dir}/ifstate", mode="w")
|
|
f.puts("#{if_name}=#{if_name}")
|
|
f.close()
|
|
else
|
|
found = false
|
|
f = File.new("#{base_dir}/ifstate", mode="r")
|
|
f.each_line do |line|
|
|
i = line.split('=')[0]
|
|
if if_name == i
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
f.close()
|
|
if ! found
|
|
f = File.new("#{base_dir}/ifstate", mode="a")
|
|
f.puts("#{if_name}=#{if_name}")
|
|
f.close()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def self.get_if_addr_mappings
|
|
if_list = {}
|
|
ip_a = iproute(['-f', 'inet', 'addr', 'show'])
|
|
if_name = nil
|
|
ip_a.each do |line|
|
|
line.rstrip!
|
|
case line
|
|
when /^\s*\d+\:\s+([\w\-\.]+)[\:\@]/i
|
|
if_name = $1
|
|
if_list[if_name] = { :ipaddr => [] }
|
|
when /^\s+inet\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/
|
|
next if if_name.nil?
|
|
if_list[if_name][:ipaddr] << $1
|
|
else
|
|
next
|
|
end
|
|
end
|
|
return if_list
|
|
end
|
|
|
|
def self.get_if_defroutes_mappings
|
|
rou_list = {}
|
|
ip_a = iproute(['-f', 'inet', 'route', 'show'])
|
|
ip_a.each do |line|
|
|
line.rstrip!
|
|
next if !line.match(/^\s*default\s+via\s+([\d\.]+)\s+dev\s+([\w\-\.]+)(\s+metric\s+(\d+))?/)
|
|
metric = $4.nil? ? :absent : $4.to_i
|
|
rou_list[$2] = { :gateway => $1, :gateway_metric => metric } if rou_list[$2].nil? # do not replace to gateway with highest metric
|
|
end
|
|
return rou_list
|
|
end
|
|
|
|
def self.get_routes
|
|
# return array of hashes -- all defined routes.
|
|
rv = []
|
|
# cat /proc/net/route returns information about routing table in format:
|
|
# Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
|
|
# eth0 00000000 0101010A 0003 0 0 0 00000000 0 0 0
|
|
# eth0 0001010A 00000000 0001 0 0 0 00FFFFFF 0 0 0
|
|
File.open('/proc/net/route').readlines.reject{|l| l.match(/^[Ii]face.+/) or l.match(/^(\r\n|\n|\s*)$|^$/)}.map{|l| l.split(/\s+/)}.each do |line|
|
|
#https://github.com/kwilczynski/facter-facts/blob/master/default_gateway.rb
|
|
iface = line[0]
|
|
metric = line[6]
|
|
# whether gateway is default
|
|
if line[1] == '00000000'
|
|
dest = 'default'
|
|
dest_addr = nil
|
|
mask = nil
|
|
route_type = 'default'
|
|
else
|
|
dest_addr = [line[1]].pack('H*').unpack('C4').reverse.join('.')
|
|
mask = [line[7]].pack('H*').unpack('B*')[0].count('1')
|
|
dest = "#{dest_addr}/#{mask}"
|
|
end
|
|
# whether route is local
|
|
if line[2] == '00000000'
|
|
gateway = nil
|
|
route_type = 'local'
|
|
else
|
|
gateway = [line[2]].pack('H*').unpack('C4').reverse.join('.')
|
|
route_type = nil
|
|
end
|
|
rv << {
|
|
:destination => dest,
|
|
:gateway => gateway,
|
|
:metric => metric.to_i,
|
|
:type => route_type,
|
|
:interface => iface,
|
|
}
|
|
end
|
|
# this sort need for prioritize routes by metrics
|
|
return rv.sort_by{|r| r[:metric]||0}
|
|
end
|
|
|
|
end
|
|
|
|
# vim: set ts=2 sw=2 et :
|