fuel-library/deployment/puppet/l23network/lib/puppet/parser/functions/generate_network_config.rb

521 lines
21 KiB
Ruby

require 'ipaddr'
require 'yaml'
require 'forwardable'
require 'puppet/parser'
require 'puppet/parser/templatewrapper'
require 'puppet/resource/type_collection'
require 'puppet/util/methodhelper'
require 'puppetx/l23network'
module L23network
def self.default_offload_set
{
'generic-receive-offload' => false,
'generic-segmentation-offload' => false
}
end
def self.correct_ethtool_set(prop_hash)
if !prop_hash.has_key?('ethtool') and (prop_hash.has_key?('vendor_specific') and prop_hash['vendor_specific']['disable_offloading'])
# add default offload settings if:
# * no ethtool properties given
# * "disable offload" flag given
rv = {}.merge prop_hash
rv['ethtool'] ||= {}
rv['ethtool']['offload'] = default_offload_set()
rv['vendor_specific'].delete('disable_offloading')
else
rv = prop_hash
end
return rv
end
def self.is_ib_interface(name)
# whether interface with name is Infiniband interface?
return name =~ /^ib\d+\.80\d+$/
end
def self.is_subinterface(name)
# whether transformation should operate with subinterface?
return (name =~ /\.\d+$/) && !L23network.is_ib_interface(name)
end
def self.sanitize_transformation(trans)
action = trans[:action].to_s.downcase()
# Setup defaults
rv = case action
when "noop" then {
:name => nil,
:provider => nil
}
when "add-br" then {
:name => nil,
:stp => nil,
:bpdu_forward => nil,
# :bridge_id => nil,
:external_ids => nil,
# :interface_properties => nil,
:delay_while_up => nil,
:vendor_specific => nil,
:provider => nil
}
when "add-port" then {
:name => nil,
:bridge => nil,
# :type => "internal",
:mtu => nil,
:ethtool => nil,
:vlan_id => nil,
:vlan_dev => nil,
# :trunks => [],
:delay_while_up => nil,
:vendor_specific => nil,
:provider => nil
}
when "add-bond" then {
:name => nil,
:bridge => nil,
:mtu => nil,
:interfaces => [],
# :vlan_id => 0,
# :trunks => [],
:delay_while_up => nil,
:bond_properties => nil,
:interface_properties => nil,
:vendor_specific => nil,
:provider => nil
}
when "add-patch" then {
:name => "unnamed", # calculated later
:bridges => [],
:vlan_ids => [0, 0],
:mtu => nil,
:vendor_specific => nil,
:provider => nil
}
else
raise(Puppet::ParseError, "Unknown transformation: '#{action}'.")
end
# replace defaults to real parameters
rv.map{|k,v| rv[k] = trans[k] if trans.has_key? k }
# Validate and mahgle highly required properties. Most of properties should be validated in puppet type.
rv[:action] = action
if not rv[:name].is_a? String
raise(Puppet::ParseError, "Unnamed transformation: '#{action}'.")
end
if action == "add-patch"
if !rv[:bridges].is_a? Array or rv[:bridges].size() != 2
raise(Puppet::ParseError, "Transformation patch have wrong 'bridges' parameter.")
end
rv[:name] = get_patch_name(rv[:bridges]) # name for patch SHOULD be auto-generated
end
return rv
end
end
Puppet::Parser::Functions::newfunction(:generate_network_config, :type => :rvalue, :doc => <<-EOS
This function get Hash of network interfaces and endpoints configuration
and realized it.
EOS
) do |argv|
def default_netmask()
"/24"
end
def create_endpoint()
{
:ipaddr => []
}
end
def res_factory()
# define internal puppet parameters for creating resources
{
:br => 'l23network::l2::bridge',
:port => 'l23network::l2::port',
:bond => 'l23network::l2::bond',
:patch => 'l23network::l2::patch',
:ifconfig => 'l23network::l3::ifconfig'
}
end
def correct_requirement_name(name)
name.split('::').map{|x| x.capitalize}.join('::')
end
def create_l3_ifconfig_resource(endpoints, endpoint_name, previous=nil)
resource_properties = { }
create_routes = []
# create resource
resource = res_factory[:ifconfig]
debug("generate_network_config(): Endpoint '#{endpoint_name}' will be created with properties: \n#{endpoints[endpoint_name].to_yaml.gsub('!ruby/sym ','')}")
# collect properties for creating endpoint resource
endpoints[endpoint_name].each do |k,v|
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)
else
resource_properties[k.to_s] = v
end
end
#Clear default gateway
#todo(sv): remove it here and include to l3_ifconfig resource
if resource_properties['gateway']
l3_resource_properties = { 'ensure' => 'absent', 'destination' => 'default', 'gateway' => resource_properties['gateway'], 'interface' => endpoint_name }
l3_resource_properties['metric'] = resource_properties['gateway_metric'] if resource_properties['gateway_metric']
gateway_name = L23network.get_route_resource_name('default', l3_resource_properties['metric'])
if previous
l3_resource_properties['require'] = [correct_requirement_name(previous)]
end
previous = "l3_clear_route[#{gateway_name}]"
debug("generate_network_config(): Routing-cleaner '#{gateway_name}' will be created for endpoint '#{endpoint_name}'")
function_create_resources(['l3_clear_route', { gateway_name => l3_resource_properties }])
end
resource_properties['require'] = [correct_requirement_name(previous)] if previous
# todo(sv): make ability add 'dhcp' resources through yaml network-scheme
# # set ipaddresses
# #if endpoints[endpoint_name][:IP].empty?
# # p_resource.set_parameter(:ipaddr, 'none')
# #elsif ['none','dhcp'].index(endpoints[endpoint_name][:IP][0])
# # p_resource.set_parameter(:ipaddr, endpoints[endpoint_name][:IP][0])
# #else
# # ipaddrs = []
# # endpoints[endpoint_name][:IP].each do |i|
# # if i =~ /\/\d+$/
# # ipaddrs.insert(-1, i)
# # else
# # ipaddrs.insert(-1, "#{i}#{default_netmask()}")
# # end
# # end
# # p_resource.set_parameter(:ipaddr, ipaddrs)
# #end
# #set another (see L23network::l3::ifconfig DOC) parametres
resource_properties = L23network.correct_ethtool_set(resource_properties)
function_create_resources([resource, {
"#{endpoint_name}" => resource_properties
}])
previous = "#{resource}[#{endpoint_name}]"
if ! create_routes.empty?
create_routes.each do |route|
next if !route.has_key?(:net) or !route.has_key?(:via)
route_properties = {
'destination' => route[:net],
'gateway' => route[:via],
'by_network_scheme' => true,
'require' => correct_requirement_name(previous)
}
route_properties[:metric] = route[:metric] if route[:metric].to_i > 0
route_name = L23network.get_route_resource_name(route[:net], route[:metric])
debug("generate_network_config(): Route resource '#{route_name}' will be created for endpoint '#{endpoint_name}'")
function_create_resources(['l23network::l3::route', {
"#{route_name}" => route_properties
}])
previous = "l23network::l3::route[#{route_name}]"
end
end
return previous
end
###
# start newfunction(:generate_network_config)
if argv.size != 0
raise(Puppet::ParseError, "generate_network_config(): Wrong number of arguments.")
end
config_hash = L23network::Scheme.get_config(lookupvar('l3_fqdn_hostname'))
if config_hash.nil?
raise(Puppet::ParseError, "generate_network_config(...): You must call prepare_network_config(...) first!")
end
# we can't imagine, that user can write in this field, but we try to convert to numeric and compare
if config_hash[:version].to_s.to_f < 1.1
raise(Puppet::ParseError, "generate_network_config(...): You network_scheme hash has wrong format.\nThis parser can work with v1.1 format, please convert you config.")
end
default_provider = config_hash[:provider] || 'lnx'
# collect interfaces and endpoints
debug("generate_network_config(): collect interfaces")
ifconfig_created = []
born_ports = []
# collect L2::port properties from 'interfaces' section
ports_properties = {} # additional parameters from interfaces was stored here
config_hash[:interfaces].each do |int_name, int_properties|
int_name = int_name.to_sym()
#endpoints[int_name] = create_endpoint()
born_ports << int_name
# add some of 1st level interface properties to it's config
ports_properties[int_name] ||= {}
if int_properties.is_a? Hash
int_properties.each do |k,v|
if v.to_s != ''
k = k.to_s.tr('-','_').to_sym
ports_properties[int_name][k] = v
end
end
end
end
# collect L3::ifconfig properties from 'endpoints' section
debug("generate_network_config(): collect endpoints")
endpoints = {}
if config_hash[:endpoints].is_a? Hash and !config_hash[:endpoints].empty?
config_hash[:endpoints].each do |e_name, e_properties|
e_name = e_name.to_sym()
endpoints[e_name] = create_endpoint()
if ! (e_properties.nil? or e_properties.empty?)
e_properties.each do |k,v|
next if k == :gateway and v.to_s.empty?
k = k.to_s.tr('-','_').to_sym
if k == :IP
v = nil if v == :undef
if !(v.is_a?(Array) || ['none','dhcp',nil].include?(v))
raise(Puppet::ParseError, "generate_network_config(): IP field for endpoint '#{e_name}' must be array of IP addresses, 'dhcp' or 'none'. Got: '#{v.inspect}'")
elsif ['none','dhcp',''].include?(v.to_s)
# 'none' and 'dhcp' should be passed to resource not as list
endpoints[e_name][:ipaddr] = (v.to_s == 'dhcp' ? 'dhcp' : 'none')
else
v.each do |ip|
begin
iip = IPAddr.new(ip) # validate IP address
endpoints[e_name][:ipaddr] ||= []
endpoints[e_name][:ipaddr] << ip
rescue
raise(Puppet::ParseError, "generate_network_config(): IP address '#{ip}' for endpoint '#{e_name}' wrong!.")
end
end
end
else
endpoints[e_name][k] = v
end
end
else
endpoints[e_name][:ipaddr] = 'none'
end
end
else
config_hash[:endpoints] = {}
end
# pre-check and auto-add main interface for sub-interface
# to transformation if required
debug("generate_network_config(): precheck transformations")
tmp = []
config_hash[:transformations].each do |t|
if (t[:action].match(/add-(port|bond)/) && L23network.is_subinterface(t[:name]))
# we found vlan subinterface, but main interface for one didn't defined
# earlier. We should configure main interface as unaddressed interface
# wich has state UP to prevent fails in network configuration
name = t[:name].split('.')[0]
if tmp.select{|x| x[:action].match(/add-(port|bond)/) && x[:name]==name}.empty?
debug("Auto-add 'add-port(#{name})' for '#{t[:name]}'")
tmp << {
:action => 'add-port',
:name => name
}
end
tmp << t
elsif t[:action] == 'add-bond'
t[:interfaces].each do |ifname|
if_sym = ifname.to_sym
b_provider=t[:provider]
mtu = t[:mtu]
if (i=tmp.index{|x| x[:action]=='add-port' && x[:name]==ifname})
# we should add something from bond's interface_properties to existing add-port in the transformations.
debug("Merge interface_properties from bond '#{t[:name]}' to 'add-port(#{ifname})'")
if t[:interface_properties].is_a?(Hash)
t[:interface_properties].each do |k,v|
if k=='mtu' and !v.nil?
mtu==v.to_i
elsif v.is_a?(Hash) and tmp[i][k].is_a?(Hash)
tmp[i][k] = v.merge(tmp[i][k])
elsif tmp[i][k].nil?
tmp[i][k] = v
end
end
end
else
# we has no bond slave in the transformation
# should be added
debug("Auto-add 'add-port(#{ifname})' for '#{t[:name]}'")
props = config_hash[:interfaces][if_sym] || {}
if t[:interface_properties].is_a?(Hash)
t[:interface_properties].each do |k,v|
if k=='mtu' and !v.nil?
mtu==v.to_i
elsif v.is_a?(Hash) and props[k].is_a?(Hash)
props[k] = v.merge(props[k])
elsif props[k].nil?
props[k] = v
end
end
end
props[:provider] = b_provider if props[:provider].nil?
tmp << {
:action => 'add-port',
:name => ifname,
:mtu => mtu
}.merge(props)
end
end
tmp << t
elsif (i=tmp.index{|x| x[:action].match(/add-(port|bond)/) && x[:name]==t[:name]})
# we has transformation for this interface already auto-added by previous
# condition. We should merge this properties into which are autocreated
# earlier by transformation and forget this.
#
# It looks like some strange reordering
tmp[i].merge! t
debug("Auto-add 'move-properties-for-port(#{t[:name]})', because one autocreated early.")
else
tmp << t
end
end
config_hash[:transformations] = tmp
debug("generate_network_config(): process transformations")
# execute transformations
resources_created = []
previous = nil
config_hash[:transformations].each do |t|
action = t[:action].strip()
if action.start_with?('add-')
action = t[:action][4..-1].to_sym()
action_ensure = nil
elsif action.start_with?('del-')
action = t[:action][4..-1].to_sym()
action_ensure = 'absent'
else
action = t[:action].to_sym()
end
#debug("TXX: '#{t[:name]}' => '#{t.to_yaml.gsub('!ruby/sym ','')}'.")
trans = L23network.sanitize_transformation(t)
if action != :noop
#debug("TTT: '#{trans[:name]}' => '#{trans.to_yaml.gsub('!ruby/sym ','')}'.")
# merge interface properties with transformations and vendor_specific
port_props = ports_properties[trans[:name].to_sym()] || {}
trans = trans.merge(port_props) { |k,a,b| (k == :vendor_specific and a.is_a?(Hash)) ? a.merge(b) : b }
if trans.has_key?(:mtu) && !trans[:name].nil? && trans[:name] != 'unnamed'
# MTU should be adjusted to phys_dev if it is possible
devices = L23network.get_phys_dev_by_transformation(trans[:name], lookupvar('l3_fqdn_hostname'))
if !devices.nil? && devices[0] != trans[:name]
if trans[:mtu].nil?
trans[:mtu] = L23network.get_property_for_transformation('MTU', devices[0], lookupvar('l3_fqdn_hostname'))
end
end
end
if !trans[:provider]
if action == :port && trans[:bridge] && ((trans[:vlan_dev] && trans[:vlan_id]) or trans[:name]=~/\.\d+/)
trans[:provider] = default_provider
elsif action == :port && trans[:bridge]
provider = L23network.get_property_for_transformation('PROVIDER', trans[:bridge], lookupvar('l3_fqdn_hostname'))
trans[:provider] = provider || default_provider
else
trans[:provider] = default_provider
end
end
# add default delay for bonds. 45 sec. for LACP bonds and 15 sec for another.
if (action == :bond) && trans[:bond_properties].is_a?(Hash) && trans[:delay_while_up].to_i == 0
delay_while_up = ( trans[:bond_properties][:mode]=='802.3ad' ? 45 : 15 )
if ['', 'absent'].include? trans[:bridge].to_s
# bond not included to bridge. I.e. has IP address or subinterfaces and post-up has sense
trans[:delay_while_up] = delay_while_up
else
# bond included to bridge. I.e. has no IP address
bridge_res = findresource("L23network::L2::Bridge[#{trans[:bridge].to_s}]")
if !bridge_res.nil? && bridge_res[:delay_while_up].nil?
bridge_res[:delay_while_up] = delay_while_up
end
end
end
# create puppet resources for interfaces and transformations.
# create endpoints, which linked to interfaces or transformations
resource = res_factory[action]
resource_properties = { }
debug("generate_network_config(): Transformation '#{trans[:name]}' will be produced as \n#{trans.to_yaml.gsub('!ruby/sym ','')}")
trans.select{|k,v| k != :action}.each do |k,v|
if ['Hash', 'Array'].include? v.class.to_s
resource_properties[k.to_s] = L23network.reccursive_sanitize_hash(v)
if action == :bond && k==:interface_properties
# search 'disable_offloading' flag and correct ethtool properties if required
resource_properties[k.to_s] = L23network.correct_ethtool_set(resource_properties[k.to_s])
end
elsif ! v.nil?
resource_properties[k.to_s] = v
else
#todo(sv): more powerfull handler for 'nil' properties
end
end
if L23network.is_ib_interface(trans[:name])
# Infiniband device is not vlan, but name looks like vlan subinterface
resource_properties[:vlan_dev] = false
end
resource_properties['require'] = [correct_requirement_name(previous)] if previous
resource_properties = L23network.correct_ethtool_set(resource_properties)
function_create_resources([resource, {
"#{trans[:name]}" => resource_properties
}])
resources_created << "#{t[:action].strip()}(#{trans[:name]})"
born_ports << trans[:name].to_sym() if action != :patch
previous = "#{resource}[#{trans[:name]}]"
end
# try to create ifconfig for newly-created resource
if endpoints.has_key? trans[:name].to_sym()
endpoint_name = trans[:name].to_sym()
ifconfig_created << endpoint_name
previous = create_l3_ifconfig_resource(endpoints, endpoint_name, previous)
resources_created << "endpoint(#{endpoint_name})"
end
end
# Calculate delta between all endpoints and already created while transformation processed
rest_ifconfig = endpoints.keys().sort() - ifconfig_created
# create resources for rest endpoints
# in alphabetical order
debug("generate_network_config(): process endpoints")
rest_ifconfig.each do |endpoint_name|
if ! born_ports.include? endpoint_name
raise(Puppet::ParseError, "generate_network_config(): Endpoint '#{endpoint_name}' not found in interfaces or transformations result.")
end
if L23network.is_ib_interface(endpoint_name) and !resources_created.include?("add-port(#{endpoint_name})")
# Infiniband device should be created as port if does not created early
ib_resource_properties = { :vlan_dev => false }
ib_resource_properties['require'] = [correct_requirement_name(previous)] if previous
function_create_resources(['l23network::l2::port', {
"#{endpoint_name}" => ib_resource_properties
}])
previous = "L23network::L2::Port[#{endpoint_name}]"
resources_created << "add-port(#{endpoint_name})"
end
previous = create_l3_ifconfig_resource(endpoints, endpoint_name, previous)
resources_created << "endpoint(#{endpoint_name})"
end
debug("generate_network_config(): done...")
return resources_created.join(" -> ")
end
# vim: set ts=2 sw=2 et :