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>] 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', #) # # => [ # # { # # :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 :