fuel-library/deployment/puppet/l23network/lib/puppet/provider/l2_base.rb

621 lines
21 KiB
Ruby

require 'puppetx/l23_utils'
require 'puppetx/l23_ethtool_name_commands_mapping'
require 'yaml'
class Puppet::Provider::L2_base < Puppet::Provider
def self.prefetch(resources)
interfaces = instances
resources.keys.each do |name|
if provider = interfaces.find{ |ii| ii.name == name }
resources[name].provider = provider
end
end
end
# ---------------------------------------------------------------------------
def self.iface_exist?(iface)
File.exist? "/sys/class/net/#{iface}"
end
def self.get_lnx_vlan_interfaces
# returns hash, that contains ports (interfaces) configuration.
# i.e {
# eth0.101 => { :vlan_dev => 'eth0', :vlan_id => 101, vlan_mode => 'eth' },
# vlan102 => { :vlan_dev => 'eth0', :vlan_id => 102, vlan_mode => 'vlan' },
# }
#
vlan_ifaces = {}
rc_c = /([\w+\.\-]+)\s*\|\s*(\d+)\s*\|\s*([\w+\-]+)/
File.open("/proc/net/vlan/config", "r").each do |line|
if (rv=line.match(rc_c))
vlan_ifaces[rv[1]] = {
:vlan_dev => rv[3],
:vlan_id => rv[2],
:vlan_mode => (rv[1].match('\.').nil? ? 'vlan' : 'eth' )
}
end
end
return vlan_ifaces
end
def self.get_lnx_ports
# returns hash, that contains ports (interfaces) configuration.
# i.e {
# eth0 => { :mtu => 1500, :if_type => :ethernet, port_type => lnx:eth:unremovable },
# }
#
# 'unremovable' flag for port_type means, that this port is a more complicated thing,
# than just a port and can't be removed just as port. For example you can't remove bond
# as port. You should remove it as bond.
#
port = {}
#
# parse 802.1q vlan interfaces from /proc
vlan_ifaces = self.get_lnx_vlan_interfaces()
# Fetch information about interfaces, visible in network namespace from /sys/class/net
interfaces = Dir['/sys/class/net/*'].select{ |f| File.symlink? f}
interfaces.each do |if_dir|
if_name = if_dir.split('/')[-1]
port[if_name] = {
:name => if_name,
:port_type => [],
:onboot => self.get_iface_state(if_name),
:ethtool => nil,
:mtu => File.open("#{if_dir}/mtu").read.chomp.to_i,
:provider => (if_name == 'ovs-system') ? 'ovs' : 'lnx' ,
}
# determine port_type for this iface
if File.directory? "#{if_dir}/bonding"
# This interface is a baster of bond, get bonding properties
port[if_name][:slaves] = File.open("#{if_dir}/bonding/slaves").read.chomp.strip.split(/\s+/).sort
port[if_name][:port_type] << 'bond' << 'unremovable'
elsif File.directory? "#{if_dir}/bridge" and File.directory? "#{if_dir}/brif"
# this interface is a bridge, get bridge properties
port[if_name][:slaves] = Dir["#{if_dir}/brif/*"].map{|f| f.split('/')[-1]}.sort
port[if_name][:port_type] << 'bridge' << 'unremovable'
else
#pass
end
# Check, whether this interface is a slave of anything
if File.symlink?("#{if_dir}/master")
port[if_name][:has_master] = File.readlink("#{if_dir}/master").split('/')[-1]
end
# Check, whether this interface is a subinterface
if vlan_ifaces.has_key? if_name
# this interface is a 802.1q subinterface
port[if_name].merge! vlan_ifaces[if_name]
port[if_name][:port_type] << 'vlan'
end
end
# Check, whether port is a slave of anything another
port.keys.each do |p_name|
if port[p_name].has_key? :has_master
master = port[p_name][:has_master]
#debug("m='#{master}', name='#{p_name}', props=#{port[p_name]}")
master_flags = port[master][:port_type]
if master_flags.include? 'bond'
# this port is a bond_member
port[p_name][:bond_master] = master
port[p_name][:port_type] << 'bond-slave'
elsif master_flags.include? 'bridge'
# this port is a member of bridge
port[p_name][:bridge] = master
port[p_name][:port_type] << 'bridge-slave'
elsif master == 'ovs-system'
port[p_name][:port_type] << 'ovs-affected'
else
#pass
end
port[p_name].delete(:has_master)
end
end
return port
end
# ---------------------------------------------------------------------------
def self.ovs_parse_opthash(hh)
#if !(hh=~/^['"]/ and hh=~/['"]$/)
rv = {}
if hh =~ /^\{(.*)\}$/
$1.split(/\s*\,\s*/).each do |pair|
k,v = pair.split('=')
#debug("===#{k}===#{v}===")
rv[k.tr("'\"",'').to_sym] = v.nil? ? nil : v.tr("'\"",'')
end
end
return rv
end
def self.get_ovs_bridges
# return OVS interfaces hash if it possible
begin
vsctl_list_bridges = vsctl('list', 'Bridge').split("\n")
vsctl_list_bridges << :EOF # last section of output should be processsed anyway.
rescue
debug("Can't find OVS ports, because error while 'ovs-vsctl list Bridge' execution")
return {}
end
#
buff = {}
rv = {}
# parse ovs-vsctl output and find OVS and OVS-affected interfaces
vsctl_list_bridges.each do |line|
if line =~ /(\w+)\s*\:\s*(.*)\s*$/
key = $1.tr("'\"",'')
val = $2.tr("'\"",'')
buff[key] = (val == '[]' ? '' : val)
elsif line =~ /^\s*$/ or line == :EOF
stp_enable = buff['stp_enable'] || ''
rv[buff['name']] = {
:stp => stp_enable.downcase == 'true',
:vendor_specific => {
:external_ids => ovs_parse_opthash(buff['external_ids']),
:other_config => ovs_parse_opthash(buff['other_config']),
:status => ovs_parse_opthash(buff['status']),
}
}
debug("Found OVS br: '#{buff['name']}' with properties: #{rv[buff['name']]}")
buff = {}
else
debug("Output of 'ovs-vsctl list Bridge' contain misformated line: '#{line}'")
end
end
return rv
end
def self.get_ovs_ports
# return OVS interfaces hash if it possible
begin
vsctl_list_ports = vsctl('list', 'Port').split("\n")
vsctl_list_ports << :EOF # last section of output should be processsed anyway.
rescue
debug("Can't find OVS ports, because error while 'ovs-vsctl list Port' execution")
return {}
end
#
buff = {}
rv = {}
# parse ovs-vsctl output and find OVS and OVS-affected interfaces
vsctl_list_ports.each do |line|
if line =~ /(\w+)\s*\:\s*(.*)\s*$/
key = $1.tr("'\"",'')
val = $2.tr("'\"",'')
buff[key] = val == '[]' ? '' : val
elsif line =~ /^\s*$/ or line == :EOF
rv[buff['name']] = {
:vendor_specific => {
:other_config => ovs_parse_opthash(buff['other_config']),
:status => ovs_parse_opthash(buff['status']),
}
}
rv[buff['name']][:vlan_id] = buff['tag'] if ! (buff['tag'].nil? or buff['tag'].empty?)
rv[buff['name']][:trunks] = buff['trunks'].tr("[]",'').split(/[\,\s]+/) if ! (buff['trunks'].nil? or buff['trunks'].empty?)
debug("Found OVS port '#{buff['name']}' with properties: #{rv[buff['name']]}")
buff = {}
else
debug("Output of 'ovs-vsctl list Port' contain misformated line: '#{line}'")
end
end
return rv
end
def self.get_ovs_interfaces
# return OVS interfaces hash if it possible
begin
vsctl_list_interfaces = vsctl('list', 'Interface').split("\n")
vsctl_list_interfaces << :EOF # last section of output should be processsed anyway.
rescue
debug("Can't find OVS interfaces, because error while 'ovs-vsctl list Interface' execution")
return {}
end
#
buff = {}
rv = {}
# parse ovs-vsctl output and find OVS and OVS-affected interfaces
vsctl_list_interfaces.each do |line|
if line =~ /(\w+)\s*\:\s*(.*)\s*$/
key = $1.tr("'\"",'')
val = $2.tr("'\"",'')
buff[key] = val == '[]' ? '' : val
elsif line =~ /^\s*$/ or line == :EOF
rv[buff['name']] = {
:mtu => buff['mtu'],
:port_type => (buff['type'].nil? or buff['type'].empty?) ? [] : [buff['type']],
:vendor_specific => {
:status => ovs_parse_opthash(buff['status']),
}
}
driver = rv[buff['name']][:vendor_specific][:status][:driver_name]
if driver.nil? or driver.empty? or driver == 'openvswitch'
rv[buff['name']][:provider] = 'ovs'
else
rv[buff['name']][:provider] = nil
end
debug("Found OVS interface '#{buff['name']}' with properties: #{rv[buff['name']]}")
buff = {}
else
debug("Output of 'ovs-vsctl list Interface' contain misformated line: '#{line}'")
end
end
return rv
end
def self.ovs_vsctl_show
begin
#content = vsctl('show')
content = `ovs-vsctl show`
rescue
debug("Can't get OVS configuration, because error while 'ovs-vsctl show' execution")
return {}
end
bridges = get_ovs_bridges()
ports = get_ovs_ports()
interfaces = get_ovs_interfaces()
ovs_config = {
:port => {},
:interface => {},
#:bond => {}, # bond in ovs is a internal only port !!!
:bridge => {},
:jack => {} # jack of ovs patchcord (patchcord is a pair of ports with type 'patch')
}
_br = nil
_po = nil
_if = nil
#_ift = nil
content.split("\n").each do |line|
line.rstrip!
case line
when /^\s+Bridge\s+"?([\w\-\.]+)\"?$/
_br = $1
_po = nil
_if = nil
ovs_config[:bridge][_br] = {
:port_type => ['bridge'],
:br_type => 'ovs',
:provider => 'ovs'
}
if bridges.has_key? _br
ovs_config[:bridge][_br].merge! bridges[_br]
end
when /^\s+Port\s+"?([\w\-\.]+)\"?$/
next if _br.nil?
_po = $1
_if = nil
ovs_config[:port][_po] = {
:bridge => _br,
:port_type => [],
#:provider => 'ovs'
}
if ports.has_key? _po
ovs_config[:port][_po].merge! ports[_po]
end
if _po == _br
ovs_config[:port][_po][:port_type] << 'bridge'
end
when /^\s+Interface\s+"?([\w\-\.]+)\"?$/
_if = $1
ovs_config[:interface][_if] = {
:port => _po,
}
if interfaces.has_key? _if
ovs_config[:interface][_if].merge! interfaces[_if]
end
#todo(sv): Check interface driver from Interfaces table
ovs_config[:port][_po][:provider] = ovs_config[:interface][_if][:provider]
when /^\s+type:\s+"?([\w\-\.]+)\"?$/
ovs_config[:interface][_if].merge!({
:type => $1
})
when /^\s+options:\s+\{(.+)\}\s*$/
opts = $1.split(/[\s\,]+/).map{|o| o.split('=')}.reduce({}){|h,p| h.merge(p[0] => p[1].tr('"',''))}
ovs_config[:interface][_if].merge!({
:options => opts
})
else
#debug("Misformated line for br='#{_br}', po='#{_po}', if='#{_if}' => '#{line}'")
end
end
ovs_config[:port].keys.each do |p_name|
# didn't use .select{...} here for backward compatibility with ruby 1.8
ifaces = ovs_config[:interface].reject{|k,v| v[:port]!=p_name}
iface = ifaces[ifaces.keys[0]]
if ifaces.size > 1
# Bond found
#ovs_config[:bond][p_name] = ovs_config[:port][p_name]
#ovs_config[:port].delete(p_name)
ovs_config[:port][p_name][:port_type] << 'bond'
ovs_config[:port][p_name][:provider] = 'ovs'
elsif iface[:type] == 'patch'
ovs_config[:port][p_name][:port_type] << 'jack'
elsif iface[:type] == 'internal'
ovs_config[:port][p_name][:port_type] << 'internal'
else
# ordinary interface found
# pass
end
# get mtu value (from one of interfaces if bond) and up it to port layer
k = ifaces.keys
if k.size > 0
ovs_config[:port][p_name][:mtu] = ifaces[k[0]][:mtu]
end
# fix port-type=vlan for tagged ports
if !ovs_config[:port][p_name][:vlan_id].nil?
ovs_config[:port][p_name][:port_type] << 'vlan'
end
end
debug("VSCTL-SHOW: #{ovs_config.to_yaml.gsub('!ruby/sym ',':')}")
return ovs_config
end
# ---------------------------------------------------------------------------
def self.get_bridge_list
# search all (LXN and OVS) bridges on the host, and return hash with mapping
# bridge_name => { bridge options }
#
bridges = {}
# obtain OVS bridges list
re_c = /^\s*([\w\-]+)/
begin
vsctl('list-br').split(/\n+/).select{|l| l.match(re_c)}.collect{|a| $1 if a.match(re_c)}.each do |br_name|
br_name.strip!
bridges[br_name] = {
:br_type => :ovs
}
end
rescue
debug("No OVS bridges found, because error while 'ovs-vsctl list-br' execution")
end
# obtain LNX bridges list
re_c = /([\w\-]+)\s+\d+/
begin
# todo(sv): using port_list instead fork subprocess
brctl('show').split(/\n+/).select{|l| l.match(re_c)}.collect{|a| $1 if a.match(re_c)}.each do |br_name|
br_name.strip!
bridges[br_name] = {
:stp => (File.open("/sys/class/net/#{br_name}/bridge/stp_state").read.strip.to_i == 1),
:external_ids => :absent,
:vendor_specific => {},
:br_type => :lnx
}
end
rescue
debug("No LNX bridges found, because error while 'brctl show' execution")
end
return bridges
end
def self.get_ovs_port_bridges_pairs
# returns hash, which map ports to it's bridge.
# i.e {
# qg37f65 => { :bridge => 'br-ex', :br_type => :ovs },
# }
#
port_mappings = {}
begin
ovs_bridges = vsctl('list-br').split(/\n+/).select{|l| l.match(/^\s*[\w\-]+/)}
rescue
debug("No OVS bridges found, because error while 'ovs-vsctl list-br' execution")
return {}
end
ovs_bridges.each do |br_name|
br_name.strip!
ovs_portlist = vsctl('list-ports', br_name).split(/\n+/).select{|l| l.match(/^\s*[\w\-]+\s*/)}
#todo: handle error
ovs_portlist.each do |port_name|
port_name.strip!
port_mappings[port_name] = {
:bridge => br_name,
:br_type => :ovs
}
end
# bridge also a port, but it don't show itself by list-ports, adding it manually
port_mappings[br_name] = {
:bridge => br_name,
:br_type => :ovs
}
end
return port_mappings
end
def self.get_lnx_port_bridges_pairs
# returns hash, which map ports to it's bridge.
# i.e {
# 'eth0' => { :bridge => 'br0', :br_type => :lnx },
# }
# This function returns all visible in default namespace ports
# (lnx and ovs (with type internal)) included to the lnx bridge
#
begin
brctl_show = brctl('show').split(/\n+/).select{|l| l.match(/^[\w\-]+\s+\d+/) or l.match(/^\s+[\w\.\-]+/)}
rescue
debug("No LNX bridges found, because error while 'brctl show' execution")
return {}
end
port_mappings = {}
br_name = nil
brctl_show.each do |line|
line.rstrip!
case line
when /^([\w\-]+)\s+[\d\.abcdef]+\s+(yes|no)\s+([\w\-\.]+$)/i
br_name = $1
port_name = $3
when /^\s+([\w\.\-]+)$/
#br_name using from previous turn
port_name = $1
else
next
end
if br_name
port_mappings[port_name] = {
:bridge => br_name,
:br_type => :lnx
}
end
end
debug("LNX ports to bridges mapping: #{port_mappings.to_yaml.gsub('!ruby/sym ',':')}")
return port_mappings
end
def self.get_port_bridges_pairs
# returns hash, which map ports to it's bridge.
# i.e {
# eth0 => { :bridge => 'br0', :br_type => :lnx },
# qg37f65 => { :bridge => 'br-ex', :br_type => :ovs },
# }
# This function returns all visible in default namespace ports
# (lnx and ovs (with type internal)) included to the lnx bridge
#
# If port included to both bridges (ovs and lnx at one time),
# i.e. using as patchcord between bridges -- this port will be
# assigned to lnx-type bridge
#
port_bridges_hash = self.get_ovs_port_bridges_pairs() # LNX bridges should overwrite OVS
port_bridges_hash.merge! self.get_lnx_port_bridges_pairs() # because by design!
end
def self.get_bridges_order_for_patch(bridges)
# if given two OVS bridges -- we should sort it by name
# if given OVS and LNX bridges -- OVS should be first.
br_type = []
[0,1].each do |i|
br_type << (File.directory?("/sys/class/net/#{bridges[i]}/bridge") ? 'lnx' : 'ovs' )
end
if br_type[0] == br_type[1]
rv = bridges.sort()
elsif br_type[0] == 'ovs'
rv = [bridges[0],bridges[1]]
else
rv = [bridges[1],bridges[0]]
end
return rv
end
# ---------------------------------------------------------------------------
def self.get_lnx_bonds
# search all LXN bonds on the host, and return hash with
# bond_name => { bond options }
#
bond = {}
bondlist = File.open("/sys/class/net/bonding_masters").read.chomp.split(/\s+/).sort
bondlist.each do |bond_name|
mode = File.open("/sys/class/net/#{bond_name}/bonding/mode").read.split(/\s+/)[0]
bond[bond_name] = {
:mtu => File.open("/sys/class/net/#{bond_name}/mtu").read.chomp.to_i,
:slaves => File.open("/sys/class/net/#{bond_name}/bonding/slaves").read.chomp.split(/\s+/).sort,
:bond_properties => {
:mode => mode,
:miimon => File.open("/sys/class/net/#{bond_name}/bonding/miimon").read.chomp,
}
}
if ['802.3ad', 'balance-xor', 'balance-tlb', 'balance-alb'].include? mode
xmit_hash_policy = File.open("/sys/class/net/#{bond_name}/bonding/xmit_hash_policy").read.split(/\s+/)[0]
bond[bond_name][:bond_properties][:xmit_hash_policy] = xmit_hash_policy
end
if mode=='802.3ad'
lacp_rate = File.open("/sys/class/net/#{bond_name}/bonding/lacp_rate").read.split(/\s+/)[0]
bond[bond_name][:bond_properties][:lacp_rate] = lacp_rate
end
bond[bond_name][:onboot] = !self.get_iface_state(bond_name).nil?
end
return bond
end
def self.lnx_bond_allowed_properties
{
:active_slave => {},
:ad_select => {},
:all_slaves_active => {},
:arp_interval => {},
:arp_ip_target => {},
:arp_validate => {},
:arp_all_targets => {},
:downdelay => {},
:updelay => {},
:fail_over_mac => {},
:lacp_rate => {:need_reassemble => true},
:miimon => {},
:min_links => {},
:mode => {:need_reassemble => true},
:num_grat_arp => {},
:num_unsol_na => {},
:packets_per_slave => {},
:primary => {},
:primary_reselect => {},
:tlb_dynamic_lb => {},
:use_carrier => {},
:xmit_hash_policy => {},
:resend_igmp => {},
:lp_interval => {}
}
end
def self.lnx_bond_allowed_properties_list
self.lnx_bond_allowed_properties.keys.sort
end
def self.ovs_bond_allowed_properties
{
:downdelay => {:property => 'bond_downdelay'},
:updelay => {:property => 'bond_updelay'},
:use_carrier => {:property => 'other_config:bond-detect-mode',
:override_integer => ['miimon', 'carrier'] },
:mode => {:property => 'bond_mode',
:allow => ['balance-slb', 'active-backup', 'balance-tcp', 'stable'] },
:lacp => {:property => 'lacp',
:allow => ['off', 'active', 'passive'] },
:lacp_rate => {:property => 'other_config:lacp_time'},
:miimon => {:property => 'other_config:bond-miimon-interval'},
:slb_rebalance_interval => {:property => 'other_config:bond-rebalance-interval'},
}
end
def self.ovs_bond_allowed_properties_list
self.ovs_bond_allowed_properties.keys.sort
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.get_ethtool_name_commands_mapping
L23network.ethtool_name_commands_mapping
end
def self.get_iface_ethtool_hash(if_name, empty_return = {})
tmp = {}
#todo(sv): wrap to begin--resque
ethtool_k = ethtool_cmd('-k', if_name)
ethtool_k.split(/\n+/).select{|l| !l.match(/(^\s+|\[fixed\]|^Features)/)}.map{|x| x.split(/[\s\:]+/)}.each do |p|
tmp[p[0]] = (p[1] == 'on')
end
return {
'offload' => tmp || empty_return
}
end
# ---------------------------------------------------------------------------
def self.set_mtu(iface, mtu=1500)
if File.symlink?("/sys/class/net/#{iface}")
debug("Set MTU to '#{mtu}' for interface '#{iface}'")
File.open("/sys/class/net/#{iface}/mtu", "a") { |f| f.write(mtu) }
end
end
end
# vim: set ts=2 sw=2 et :