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

561 lines
17 KiB
Ruby

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
# @return [String] The path to network-script directory on redhat systems
def self.script_directory
'/etc/network/interfaces.d'
end
def self.target_files(script_dir = nil)
provider = self.name
debug("Collecting target files for #{provider}")
entries = super
regoc_regex = %r{ovs_type}
if provider =~ /ovs_/
entries.select! { |entry| !open(entry).grep(regoc_regex).empty? }
elsif provider =~ /lnx_/
entries.select! { |entry| open(entry).grep(regoc_regex).empty? }
end
entries
end
def self.property_mappings
{
:if_type => 'if_type', # pseudo field, not found in config, but calculated
:if_provider => 'if_provider', # pseudo field, not found in config, but calculated
:method => 'method',
:name => 'iface',
:onboot => 'auto',
:mtu => 'mtu',
:bridge_ports => 'bridge_ports', # ports, members of bridge, fake property
:bridge_stp => 'bridge_stp',
:vlan_dev => 'vlan-raw-device',
:ipaddr => 'address',
# :netmask => 'netmask',
:gateway => 'gateway',
:gateway_metric => 'metric', # todo: rename to 'metric'
# :dhcp_hostname => 'hostname'
:bond_master => 'bond-master',
:bond_slaves => 'bond-slaves',
:bond_mode => 'bond-mode',
:bond_miimon => 'bond-miimon',
:bond_lacp => '', # unused for lnx
:bond_lacp_rate => 'bond-lacp-rate',
:bond_updelay => 'bond-updelay',
:bond_downdelay => 'bond-downdelay',
:bond_ad_select => 'bond-ad-select',
:bond_xmit_hash_policy => 'bond-xmit-hash-policy'
}
end
def property_mappings
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 [metric NN]
: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+)(\s+metric\s+\d+)?/,
:detect_shift => 3,
},
:ethtool => {
# post-up ethtool -K eth2 property [on|off]
:detect_re => /(post-)?up\s+ethtool\s+(-\w+)\s+([\w\-]+)\s+(\w+)\s+(\w+)/,
:detect_shift => 2,
},
:ipaddr_aliases => {
# ip addr add 192.168.1.43/24 dev $IFACE
:detect_re => /(post-)?up\s+ip\s+a([dr]+)?\s+add\s+(\d+\.\d+\.\d+\.\d+\/\d+)\s+dev\s+([\w\-]+)/,
:detect_shift => 3,
},
:delay_while_up => {
# post-up sleep 10
:detect_re => /(post-)?up\s+sleep\s+(\d+)/,
:detect_shift => 2,
},
}
end
def collected_properties
self.class.collected_properties
end
# Some properties can be defined as repeatable key=value string part in the
# one option in config file these properties should be fetched by RE-scanning
#
def self.oneline_properties
{ }
end
def oneline_properties
self.class.oneline_properties
end
# In the interface config files those fields should be written as boolean
def self.boolean_properties
[
:onboot,
:hotplug,
:bridge_stp
]
end
def boolean_properties
self.class.boolean_properties
end
def self.properties_fake
[
:onboot,
:name,
:family,
:method,
:if_provider
]
end
def properties_fake
self.class.properties_fake
end
# This is a hook method that will be called by PuppetX::Filemapper
#
# @param [String] filename The path of the interfaces file being parsed
# @param [String] contents The contents of the given file
#
# @return [Array<Hash<Symbol, String>>] A single element array containing
# the key/value pairs of properties parsed from the file.
#
# @example
# RedhatProvider.parse_file('/etc/sysconfig/network-scripts/ifcfg-eth0', #<String:0xdeadbeef>)
# # => [
# # {
# # :name => 'eth0',
# # :ipaddress => '169.254.0.1',
# # :netmask => '255.255.0.0',
# # },
# # ]
def self.parse_file(filename, contents)
# WARNING!!!
# this implementation can parce only one interface per file file format
# Split up the file into lines
lines = contents.split("\n")
# Strip out all comments
lines.map! { |line| line.sub(/#.*$/, '') }
# Remove all blank lines
lines.reject! { |line| line.match(/^\s*$/) }
# initialize hash as predictible values
hash = {}
hash['auto'] = false
hash['if_provider'] = 'lnx'
hash['if_type'] = :ethernet
dirty_iface_name = nil
if (m = filename.match(%r/ifcfg-(\S+)$/))
# save iface name from file name. One will be used if iface name not defined inside config.
dirty_iface_name = m[1].strip
end
# Convert the data into key/value pairs
pair_regex = %r/^\s*([\w+\-]+)\s+(.*)\s*$/
lines.each do |line|
if (m = line.match(pair_regex))
key = m[1].strip
val = m[2].strip
case key
# Ubuntu has non-linear config format. Some options should be calculated evristically
when /auto/
ooper = $1
if ! hash.has_key?('iface')
# setup iface name if it not given in iface directive
mm = val.split(/\s+/)
hash['iface'] = mm[0]
end
hash['auto'] = true
hash['if_provider'] ||= "lnx"
when /allow-(\S+)/
if $1 == 'ovs'
hash['if_provider'] = "ovs"
hash['if_type'] = "bridge"
end
if ! hash.has_key?('iface')
# setup iface name if it not given in iface directive
mm = val.split(/\s+/)
hash['iface'] = mm[0]
end
when /(ovs_\S)/
hash['if_provider'] = "ovs" if ! (hash['if_provider'] =~ /ovs/)
hash[key] = val
if key == 'ovs_bonds'
hash['if_type'] = 'bond'
end
when /iface/
mm = val.split(/\s+/)
hash['iface'] = mm[0]
hash['method'] = mm[2]
# if hash['iface'] =~ /^br.*/i
# # todo(sv): Make more powerful methodology for recognizind Bridges.
# hash['if_type'] = :bridge
# end
when /bridge-ports/
hash['if_type'] = :bridge
hash[key] = val
when /bond-(slaves|mode)/
hash['if_type'] = :bond
hash[key] = val
else
hash[key] = val
end
else
raise Puppet::Error, %{#{filename} is malformed; "#{line}" did not match "#{pair_regex.to_s}"}
end
hash
end
# set mostly low-priority interface name if not given in config file
hash['iface'] ||= dirty_iface_name
props = self.mangle_properties(hash)
# scan for one-line properties set
props.reject{|x| !oneline_properties.keys.include?(x)}.each do |key, line|
_k = Regexp.quote(oneline_properties[key][:field])
line =~ /#{_k}=(\S+)/
val = $1
props[key] = val
end
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
props.merge!({:provider => self.name})
# 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] : [])
debug("parse_file('#{filename}'): #{props.inspect}")
rv
end
def self.check_if_provider(if_data)
raise Puppet::Error, "self.check_if_provider(if_data) Should be implemented in more specific class."
end
def self.mangle_properties(pairs)
props = {}
# Unquote all values
pairs.each_pair do |key, val|
next if ! (val.is_a? String or val.is_a? Symbol)
if (munged = val.to_s.gsub(/['"]/, ''))
pairs[key] = munged
end
end
# For each interface attribute that we recognize it, add the value to the
# hash with our expected label
property_mappings.each_pair do |type_name, in_config_name|
if (val = pairs[in_config_name])
# We've recognized a value that maps to an actual type property, delete
# it from the pairs and copy it as an actual property
mangle_method_name="mangle__#{type_name}"
if self.respond_to?(mangle_method_name)
rv = self.send(mangle_method_name, val)
else
rv = val
end
props[type_name] = rv if ! [nil, :absent].include? rv
end
end
#!# # For all of the remaining values, blindly toss them into the options hash.
#!# props[:options] = pairs if ! pairs.empty?
boolean_properties.each do |bool_property|
if props[bool_property]
props[bool_property] = ! (props[bool_property].to_s =~ /^\s*(yes|on|true)\s*$/i).nil?
else
props[bool_property] = :absent
end
end
props
end
def self.mangle__method(val)
val.to_sym
end
def self.mangle__if_type(val)
val.downcase.to_sym
end
def self.mangle__gateway_metric(val)
(val.to_i == 0 ? :absent : val.to_i)
end
def self.mangle__bridge_ports(val)
val.split(/[\s,]+/).sort
end
def self.mangle__bond_slaves(val)
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|
if d[2]
metric = d[2].split(/\s+/)[-1].to_i
else
metric = 0
end
name = L23network.get_route_resource_name(d[0], metric)
rv[name] = {
'destination' => d[0],
'gateway' => d[1]
}
rv[name][:metric] = metric if metric > 0
end
return rv
end
def self.mangle__ipaddr_aliases(data)
# incoming data is list of 3-element lists:
# [network, gateway, metric]
# metric is optional
rv = []
data.each do |d|
rv << d[0]
end
return rv.sort
end
def self.mangle__ethtool(data)
# incoming data is list of 3-element lists:
# [key, interface, abbrv, value]
rv = {}
data.each do |record|
# use .reject bellow for compatibilities with ruby 1.8
section = L23network.ethtool_name_commands_mapping.reject{|k,v| v['__section_key_set__']!=record[0]}
next if section.empty?
section_name = section.keys[0]
key_fullname = section[section_name].reject{|k, v| v!=record[2]}.to_a
next if key_fullname.empty?
key_fullname = key_fullname[0][0]
next if key_fullname.to_s == ''
rv[section_name] ||= {}
rv[section_name][key_fullname] = (record[3]=='on')
end
return rv
end
def self.mangle__delay_while_up(data)
# incoming data is sleep delay
# if multiple sleeps present we should sum are delays
rv = 0
data.each do |record|
rv += record[0].to_i
end
return rv
end
###
# Hash to file
def self.iface_file_header(provider)
raise Puppet::Error, "self.iface_file_header(provider) Should be implemented in more specific class."
end
def self.format_file(filename, providers)
if providers.length == 0
return ""
elsif providers.length > 1
raise Puppet::DevError, "Unable to support multiple interfaces [#{providers.map(&:name).join(',')}] in a single file #{filename}"
end
provider = providers[0]
content, props = iface_file_header(provider)
property_mappings.reject{|k,v| (properties_fake.include?(k) or v.empty?)}.keys.each do |type_name|
next if props.has_key? type_name
val = provider.send(type_name)
val = false if ( val.is_a?(Array) and val.reject{ |x| x.to_s == 'absent' }.empty? )
if val and val.to_s != 'absent'
props[type_name] = val
end
end
debug("format_file('#{filename}')::properties: #{props.inspect}")
pairs = self.unmangle_properties(provider, props)
pairs.each_pair do |key, val|
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?) and data.to_s != 'absent')
mangle_method_name="unmangle__#{type_name}"
if self.respond_to?(mangle_method_name)
rv = self.send(mangle_method_name, provider, data)
end
content += rv if ! (rv.nil? or rv.empty?)
end
end
debug("format_file('#{filename}')::content: #{content.inspect}")
content << ''
content.join("\n")
end
def self.unmangle_properties(provider, props)
pairs = {}
boolean_properties.each do |bool_property|
if ! props[bool_property].nil?
props[bool_property] = ((props[bool_property].to_s.to_sym == :true) ? 'yes' : 'no')
end
end
#Unmangling values for ordinary properties.
property_mappings.each_pair do |type_name, in_config_name|
if (val = props[type_name])
props.delete(type_name)
mangle_method_name="unmangle__#{type_name}"
if self.respond_to?(mangle_method_name)
rv = self.send(mangle_method_name, provider, val)
else
rv = val
end
# assembly one-line option set
if oneline_properties.has_key? type_name
_key = oneline_properties[type_name][:store_to]
pairs[_key] ||= ''
pairs[_key] += "#{oneline_properties[type_name][:field]}=#{rv} "
else
pairs[in_config_name] = rv if ! [nil, :absent].include? rv
end
end
end
pairs
end
def self.unmangle__ipaddr(provider, val)
(val.to_s.downcase == 'dhcp') ? nil : val
end
def self.unmangle__if_type(provider, val)
# in Debian family interface config file don't contains declaration of interface type
nil
end
def self.unmangle__gateway_metric(provider, val)
(val.to_i == 0 ? :absent : val.to_i)
end
def self.unmangle__bridge_ports(provider, val)
if val.size < 1 or [:absent, :undef].include? Array(val)[0].to_sym
nil
else
val.sort.join(' ')
end
end
def self.unmangle__bond_master(provider, val)
if [:none, :absent, :undef].include? val.to_sym
nil
else
val
end
end
def self.unmangle__bond_slaves(provider, val)
if val.size < 1 or [:absent, :undef].include? Array(val)[0].to_sym
nil
else
val.sort.join(' ')
end
end
def self.unmangle__routes(provider, 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|
mmm = (rou['metric'].nil? ? '' : "metric #{rou['metric']} ")
rv << "post-up ip route add #{rou['destination']} via #{rou['gateway']} #{mmm} | true # #{name}"
end
rv
end
def self.unmangle__ipaddr_aliases(provider, data)
# should generate set of lines:
# "post-up ip addr add 192.168.1.43/24 dev $IFACE| true"
return [] if ['', 'absent'].include? data.to_s
rv = []
data.each do |cidr|
next if ['', 'absent'].include? cidr.to_s
rv << "post-up ip addr add #{cidr} dev #{provider.name} | true "
end
rv
end
def self.unmangle__delay_while_up(provider, data)
# should generate one line:
# "post-up sleep NN"
return [] if ['', 'absent'].include? data.to_s
["post-up sleep #{data[0]}"]
end
def self.unmangle__ethtool(provider, data)
# should generate set of lines:
# "post-up ethtool -K %interface_name% property [on|off]"
return [] if ['', 'absent'].include? data.to_s
rv = []
data.each do |section_name, rules|
next if L23network.ethtool_name_commands_mapping[section_name].nil?
section_key = L23network.ethtool_name_commands_mapping[section_name]['__section_key_set__']
next if section_key.nil?
rules.each do |k,v|
next if L23network.ethtool_name_commands_mapping[section_name][k].nil?
iface=provider.name
val = (v==true ? 'on' : 'off')
rv << "post-up ethtool #{section_key} #{iface} #{L23network.ethtool_name_commands_mapping[section_name][k]} #{val} | true # #{k}"
end
end
return rv
end
end
# vim: set ts=2 sw=2 et :