L23network::L3::Route and L3_route resources

This need for:
* re-implement multiple-l2
* stored_config for ethtool

done for l3_route:
+ prefetch
+ create
+ modify fields
+ delete
+ stored_config for Ubuntu

Fuel-CI: disable
Change-Id: Ic2e8fcd341c1e0d6281ceb3709b81a095d2d5b06
Blueprint: refactor-l23-linux-bridges
Partial-Bug: #1429190
This commit is contained in:
Sergey Vasilenko 2015-03-10 17:31:12 +03:00
parent 88abfed58f
commit 2481248e9e
10 changed files with 527 additions and 16 deletions

View File

@ -294,6 +294,7 @@ Puppet::Parser::Functions::newfunction(:generate_network_config, :type => :rvalu
# create resources for endpoints
# in order, defined by transformation
debug("generate_network_config(): process endpoints")
create_routes=[]
full_ifconfig_order.each do |endpoint_name|
if endpoints[endpoint_name]
resource_properties = { }
@ -303,7 +304,13 @@ Puppet::Parser::Functions::newfunction(:generate_network_config, :type => :rvalu
debug("generate_network_config(): Endpoint '#{endpoint_name}' will be created with additional properties \n#{endpoints[endpoint_name].to_yaml.gsub('!ruby/sym ',':')}")
# collect properties for creating endpoint resource
endpoints[endpoint_name].each_pair do |k,v|
if ['Hash', 'Array'].include? v.class.to_s
if k.to_s.downcase == 'routes'
# for routes we should create additional resource, not a property of ifconfig
next if ! v.is_a?(Array)
v.each do |vv|
create_routes << vv
end
elsif ['Hash', 'Array'].include? v.class.to_s
resource_properties[k.to_s] = L23network.reccursive_sanitize_hash(v)
elsif ! v.nil?
resource_properties[k.to_s] = v
@ -337,6 +344,21 @@ Puppet::Parser::Functions::newfunction(:generate_network_config, :type => :rvalu
end
end
debug("generate_network_config(): process additional routes")
create_routes.each do |route|
next if !route.has_key?(:net) or !route.has_key?(:via)
route_properties = {
'destination' => route[:net],
'gateway' => route[:via]
}
route_properties[:metric] = route[:metric] if route[:metric].to_i > 0
route_name = L23network.get_route_resource_name(route[:net], route[:metric])
function_create_resources(['l23network::l3::route', {
"#{route_name}" => route_properties
}])
transformation_success << "route_for(#{route[:net]})"
end
debug("generate_network_config(): done...")
return transformation_success.join(" -> ")
end

View File

@ -0,0 +1,13 @@
require 'puppetx/l23_utils'
#
module Puppet::Parser::Functions
newfunction(:get_route_resource_name, :type => :rvalue) do |argv|
if argv.size < 1 and argv.size > 2
raise(Puppet::ParseError, "get_route_resource_name(): Wrong arguments given. " +
"Should be network CIDR (or default) and optionat metric positive value.")
end
L23network.get_route_resource_name(argv[0], argv[1])
end
end
# vim: set ts=2 sw=2 et :

View File

@ -1,3 +1,5 @@
require 'puppetx/l23_utils'
require 'puppetx/l23_ethtool_name_commands_mapping'
require File.join(File.dirname(__FILE__), 'l23_stored_config_base')
class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_config_base
@ -33,6 +35,27 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
self.class.property_mappings
end
# Some resources can be defined as repeatable strings in the config file
# these properties should be fetched by RE-scanning and converted to array
def self.collected_properties
{
:routes => {
# post-up ip route add (default/10.20.30.0/24) via 1.2.3.4
:detect_re => /(post-)?up\s+ip\s+r([oute]+)?\s+add\s+(default|\d+\.\d+\.\d+\.\d+\/\d+)\s+via\s+(\d+\.\d+\.\d+\.\d+)/,
:detect_shift => 3,
},
:ethtool => {
# post-up ethtool -K eth2 prop bool_val
:detect_re => /(post-)?up\s+ethtool\s+-K\s+([\w\-]+)\s+(\w+)\s+(\w+)/,
:detect_shift => 2,
:store_format => "post-up ethtool -K % % % | true"
},
}
end
def collected_properties
self.class.collected_properties
end
# In the interface config files those fields should be written as boolean
def self.boolean_properties
[
@ -143,6 +166,27 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
props = self.mangle_properties(hash)
props.merge!({:family => :inet})
# collect properties, defined as repeatable strings
collected=[]
lines.each do |line|
rv = []
collected_properties.each_pair do |r_name, rule|
if rg=line.match(rule[:detect_re])
props[r_name] ||= []
props[r_name] << rg[rule[:detect_shift]..-1]
collected << r_name if !collected.include? r_name
next
end
end
end
# mangle collected properties if ones has specific method for it
collected.each do |prop_name|
mangle_method_name="mangle__#{prop_name}"
rv = (self.respond_to?(mangle_method_name) ? self.send(mangle_method_name, props[prop_name]) : props[prop_name])
props[prop_name] = rv if ! ['', 'absent'].include? rv.to_s.downcase
end
# The FileMapper mixin expects an array of providers, so we return the
# single interface wrapped in an array
rv = (self.check_if_provider(props) ? [props] : [])
@ -216,6 +260,32 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
val.split(/[\s,]+/).sort
end
def self.mangle__routes(data)
# incoming data is list of 3-element lists:
# [network, gateway, metric]
# metric is optional
rv = {}
data.each do |d|
name = L23network.get_route_resource_name(d[0], d[2])
rv[name] = {
:destination => d[0],
:gateway => d[1]
}
rv[name][:metric] = d[2] if d[2]
end
return rv
end
def self.mangle__ethtool(data)
# incoming data is list of 3-element lists:
# [interface, abbrv, value]
rv = {}
data.each do |d|
end
return rv
end
###
# Hash to file
@ -257,6 +327,19 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
content << "#{key} #{val}" if ! val.nil?
end
#add to content unmangled collected-properties
collected_properties.keys.each do |type_name|
data = provider.send(type_name)
if !(data.nil? or data.empty?)
mangle_method_name="unmangle__#{type_name}"
if self.respond_to?(mangle_method_name)
rv = self.send(mangle_method_name, data)
end
content += rv if ! (rv.nil? or rv.empty?)
end
end
debug("format_file('#{filename}')::content: #{content.inspect}")
content << ''
content.join("\n")
@ -272,6 +355,7 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
end
end
#will_unmangling
property_mappings.each_pair do |type_name, in_config_name|
if (val = props[type_name])
props.delete(type_name)
@ -334,5 +418,16 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
end
end
def self.unmangle__routes(data)
# should generate set of lines:
# "post-up ip route add % via % | true"
return [] if ['', 'absent'].include? data.to_s
rv = []
data.each_pair do |name, rou|
rv << "post-up ip route add #{rou[:destination]} via #{rou[:gateway]} | true # #{name}"
end
rv
end
end
# vim: set ts=2 sw=2 et :

View File

@ -0,0 +1,172 @@
require 'ipaddr'
require 'yaml'
require 'puppetx/l23_utils'
Puppet::Type.type(:l3_route).provide(:lnx) do
defaultfor :osfamily => :linux
commands :iproute => 'ip'
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.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
def self.instances
rv = []
routes = get_routes()
routes.each do |route|
name = L23network.get_route_resource_name(route[:destination], route[:metric])
props = {
:ensure => :present,
:name => name,
}
props.merge! route
props.delete(:metric) if props[:metric] == 0
debug("PREFETCHED properties for '#{name}': #{props}")
rv << new(props)
end
return rv
end
def exists?
@property_hash[:ensure] == :present
end
def create
debug("CREATE resource: #{@resource}")
@property_flush = {}.merge! @resource
#todo(sv): check accessability of gateway.
cmd = ['route', 'add', @resource[:destination], 'via', @resource[:gateway]]
cmd << ['metric', @resource[:metric]] if @resource[:metric] != :absent && @resource[:metric].to_i > 0
iproute(cmd)
@old_property_hash = {}
@old_property_hash.merge! @resource
end
def destroy
debug("DESTROY resource: #{@resource}")
cmd = ['--force', 'route', 'del', @resource[:destination], 'via', @resource[:gateway]]
cmd << ['metric', @resource[:metric]] if @resource[:metric] != :absent && @resource[:metric].to_i > 0
iproute(cmd)
@property_hash.clear
end
def initialize(value={})
super(value)
@property_flush = {}
@old_property_hash = {}
@old_property_hash.merge! @property_hash
end
def flush
if ! @property_flush.empty?
debug("FLUSH properties: #{@property_flush}")
#
# FLUSH changed properties
if @property_flush.has_key? :gateway
# gateway can't be "absent" by design
#debug("RES: '#{@resource[:gateway]}', OLD:'#{@old_property_hash[:gateway]}', FLU:'#{@property_flush[:gateway]}'")
if @old_property_hash[:gateway] != @property_flush[:gateway]
cmd = ['route', 'change', @resource[:destination], 'via', @property_flush[:gateway]]
cmd << ['metric', @resource[:metric]] if @resource[:metric] != :absent && @resource[:metric].to_i > 0
iproute(cmd)
end
end
@property_hash = resource.to_hash
end
end
#-----------------------------------------------------------------
def destination
@property_hash[:destination] || :absent
end
def destination=(val)
@property_flush[:destination] = val
end
def gateway
@property_hash[:gateway] || :absent
end
def gateway=(val)
@property_flush[:gateway] = val
end
def metric
@property_hash[:metric] || :absent
end
def metric=(val)
@property_flush[:metric] = val
end
def interface
@property_hash[:interface] || :absent
end
def interface=(val)
@property_flush[:interface] = val
end
def type
@property_hash[:type] || :absent
end
def type=(val)
@property_flush[:type] = val
end
def vendor_specific
@property_hash[:vendor_specific] || :absent
end
def vendor_specific=(val)
nil
end
#-----------------------------------------------------------------
end
# vim: set ts=2 sw=2 et :

View File

@ -1,6 +1,8 @@
# type for managing persistent interface config options
# Inspired by puppet-network module. Adrien, thanks.
require 'ipaddr'
Puppet::Type.newtype(:l23_stored_config) do
@doc = "Manage lines in interface config file"
desc @doc
@ -197,6 +199,7 @@ Puppet::Type.newtype(:l23_stored_config) do
end
newproperty(:bond_mode)
newproperty(:bond_xmit_hash_policy)
newproperty(:bond_miimon)
newproperty(:bond_lacp_rate)
@ -222,6 +225,50 @@ Puppet::Type.newtype(:l23_stored_config) do
# end
# end
newproperty(:routes) do
desc "routes, corresponded to this interface. This-a R/O property, that autofill from L3_route resource"
#defaultto {} # no default value should be!!!
validate do |val|
if ! val.is_a? Hash
fail("routes should be a hash!")
end
end
munge do |value|
L23network.reccursive_sanitize_hash(value)
end
def should_to_s(value)
"\n#{value.to_yaml.gsub('!ruby/sym','')}\n"
end
def is_to_s(value)
"\n#{value.to_yaml.gsub('!ruby/sym','')}\n"
end
end
newproperty(:ethtool) do
desc "ethtool addition configuration for this interface"
#defaultto {} # no default value should be!!!
validate do |val|
if ! val.is_a? Hash
fail("ethtool commands should be a hash!")
end
end
munge do |value|
L23network.reccursive_sanitize_hash(value)
end
def should_to_s(value)
"\n#{value.to_yaml.gsub('!ruby/sym','')}\n"
end
def is_to_s(value)
"\n#{value.to_yaml.gsub('!ruby/sym','')}\n"
end
end
newproperty(:vendor_specific) do
desc "Hash of vendor specific properties"
#defaultto {} # no default value should be!!!
@ -237,11 +284,11 @@ Puppet::Type.newtype(:l23_stored_config) do
end
def should_to_s(value)
"\n#{value.to_yaml}\n"
"\n#{value.to_yaml.gsub('!ruby/sym ','')}\n"
end
def is_to_s(value)
"\n#{value.to_yaml}\n"
"\n#{value.to_yaml.gsub('!ruby/sym ','')}\n"
end
def insync?(value)
@ -251,22 +298,35 @@ Puppet::Type.newtype(:l23_stored_config) do
def generate
return if ! (!([:absent, :none, :nil, :undef] & self[:bridge]).any? \
and [:ethernet, :bond].include? self[:if_type]
)
self[:bridge].each do |bridge|
br = self.catalog.resource('L23_stored_config', bridge)
fail("Stored_config resource for bridge '#{bridge}' not found for port '#{self[:name]}'!") if ! br
br[:bridge_ports] ||= []
ports = br[:bridge_ports]
return if ! ports.is_a? Array
if ! ports.include? self[:name]
ports << self[:name].to_s
br[:bridge_ports] = ports.reject{|a| a=='none'}.sort
if (!([:absent, :none, :nil, :undef] & self[:bridge]).any? and [:ethernet, :bond].include? self[:if_type])
self[:bridge].each do |bridge|
br = self.catalog.resource('L23_stored_config', bridge)
fail("Stored_config resource for bridge '#{bridge}' not found for port '#{self[:name]}'!") if ! br
br[:bridge_ports] ||= []
ports = br[:bridge_ports]
return if ! ports.is_a? Array
if ! ports.include? self[:name]
ports << self[:name].to_s
br[:bridge_ports] = ports.reject{|a| a=='none'}.sort
end
end
end
# find routes, that should be applied while this interface UP
if !['', 'none', 'absent'].include?(self[:ipaddr].to_s.downcase)
l3_routes = self.catalog.resources.reject{|nnn| nnn.ref.split('[')[0]!='L3_route'}
our_network = IPAddr.new(self[:ipaddr].to_s.downcase)
l3_routes.each do |rou|
if our_network.include? rou[:gateway]
self[:routes] ||= {}
self[:routes][rou[:name]] = {
:gateway => rou[:gateway],
:destination => rou[:destination]
}
self[:routes][rou[:name]][:metric] = rou[:metric] if !['', 'absent'].include? rou[:metric].to_s.downcase
end
end
end
nil
end
end
# vim: set ts=2 sw=2 et :

View File

@ -1,5 +1,7 @@
# type for managing runtime IP addresses and another L3 stuff.
require 'yaml'
Puppet::Type.newtype(:l3_ifconfig) do
@doc = "Manage a network port abctraction."
desc @doc

View File

@ -0,0 +1,108 @@
# type for managing routes in runtime.
require 'yaml'
Puppet::Type.newtype(:l3_route) do
@doc = "Manage a network routings."
desc @doc
ensurable
newparam(:name) # workarround for following error:
# Error 400 on SERVER: Could not render to pson: undefined method `merge' for []:Array
# http://projects.puppetlabs.com/issues/5220
newproperty(:destination) do
desc "Destination network"
validate do |val|
val.strip!
if val.to_s.downcase != 'default'
raise ArgumentError, "Invalid IP address: '#{val}'" if \
not val.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\d{1,2}))?$/) \
or not ($1.to_i >= 0 and $1.to_i <= 255) \
or not ($2.to_i >= 0 and $2.to_i <= 255) \
or not ($3.to_i >= 0 and $3.to_i <= 255) \
or not ($4.to_i >= 0 and $4.to_i <= 255) \
or not ($6.to_i >= 0 and $6.to_i <= 32)
end
end
end
newproperty(:gateway) do
desc "Gateway"
newvalues(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)
validate do |val|
# gateway can't be "absent" by design
val.strip!
raise ArgumentError, "Invalid gateway: '#{val}'" if \
not val.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) \
or not ($1.to_i >= 0 and $1.to_i <= 255) \
or not ($2.to_i >= 0 and $2.to_i <= 255) \
or not ($3.to_i >= 0 and $3.to_i <= 255) \
or not ($4.to_i >= 0 and $4.to_i <= 255)
end
end
newproperty(:metric) do
desc "Route metric"
newvalues(/^\d+$/, :absent, :none, :undef, :nil)
aliasvalue(:none, :absent)
aliasvalue(:undef, :absent)
aliasvalue(:nil, :absent)
aliasvalue(0, :absent)
defaultto :absent
validate do |val|
min_metric = 0
max_metric = 65535
if ! (val.to_s == 'absent' or (min_metric .. max_metric).include?(val.to_i))
raise ArgumentError, "'#{val}' is not a valid metric (must be a integer value in range (#{min_metric} .. #{max_metric})"
end
end
munge do |val|
((val == :absent) ? :absent : val.to_i)
end
end
newproperty(:interface) do
newvalues(/^[a-z_][0-9a-z\.\-\_]*[0-9a-z]$/)
desc "The interface name"
end
newproperty(:vendor_specific) do
desc "Hash of vendor specific properties"
#defaultto {} # no default value should be!!!
# provider-specific properties, can be validating only by provider.
validate do |val|
if ! val.is_a? Hash
fail("Vendor_specific should be a hash!")
end
end
munge do |value|
L23network.reccursive_sanitize_hash(value)
end
def should_to_s(value)
"\n#{value.to_yaml}\n"
end
def is_to_s(value)
"\n#{value.to_yaml}\n"
end
def insync?(value)
should_to_s(value) == should_to_s(should)
end
end
newproperty(:type) do
desc "RO field, type of route"
end
autorequire(:l2_port) do
[self[:interface]]
end
end
# vim: set ts=2 sw=2 et :

View File

@ -97,5 +97,9 @@ module L23network
# return rv
# end
def self.get_route_resource_name(dest, metric)
(metric.to_i > 0 ? "#{dest},metric:#{metric}" : rv = "#{dest}")
end
end
# vim: set ts=2 sw=2 et :

View File

@ -128,6 +128,7 @@ define l23network::l2::bond (
mtu => $mtu,
onboot => $onboot,
bond_mode => $real_bond_properties[mode],
bond_xmit_hash_policy => $real_bond_properties[xmit_hash_policy],
bond_master => undef,
bond_slaves => $interfaces,
bond_miimon => $real_bond_properties[miimon],

View File

@ -0,0 +1,34 @@
# == Define: l23network::l3::route
define l23network::l3::route (
$destination, # should be CIDR or 'default'
$gateway, # should be IP address
$metric = undef,
$vendor_specific = undef,
$provider = undef,
$ensure = present,
) {
include ::l23network::params
$r_name = get_route_resource_name($destination, $metric)
if ! defined (L3_route[$r_name]) {
if $provider {
$config_provider = "${provider}_${::l23_os}"
} else {
$config_provider = undef
}
# There are no stored_config for this resource. Configure runtime only.
l3_route { $r_name :
ensure => $ensure,
destination => $destination,
gateway => $gateway,
metric => $metric,
vendor_specific => $vendor_specific,
provider => $provider # For L3 features provider independed from OVS
}
L3_ifconfig<||> -> L3_route<||>
}
}