fix OVS-bond config file format

and some unit tests

Change-Id: I61249b7c24c4bb0b9bb76875aff9ad905eb9b969
Closes-bug: #1481951
This commit is contained in:
Sergey Vasilenko 2015-08-07 11:18:17 +03:00
parent 1851b4dff7
commit 1a369e8029
15 changed files with 423 additions and 31 deletions

View File

@ -1 +1,3 @@
spec/fixtures/*
spec/fixtures/modules/stdlib
spec/fixtures/modules/sysctl
spec/fixtures/modules/filemapper

View File

@ -12,12 +12,47 @@ Puppet::Type.type(:l23_stored_config).provide(:ovs_ubuntu, :parent => Puppet::Pr
def self.property_mappings
rv = super
rv.merge!({
:ovs_type => 'ovs_type',
:ovs_bridge => 'ovs_bridge',
#todo(sv): :onboot => '', # should not be used (may be if no ipaddr)
:ovs_type => 'ovs_type',
:bridge => 'ovs_bridge',
:bond_slaves => 'ovs_bonds',
:bond_mode => 'ovs_options',
:bond_miimon => 'ovs_options',
:bond_lacp_rate => 'ovs_options',
:bond_lacp => 'ovs_options',
:bond_xmit_hash_policy => '', # unused
})
return rv
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
{
:bond_mode => {
:field => 'bond_mode',
:store_to => 'ovs_options'
},
:bond_lacp => {
:field => 'lacp',
:store_to => 'ovs_options'
},
:bond_lacp_rate => {
:field => 'other_config:lacp-time',
:store_to => 'ovs_options'
},
:bond_miimon => {
:field => 'other_config:bond-miimon-interval',
:store_to => 'ovs_options'
},
}
end
def oneline_properties
self.class.collected_properties
end
def self.check_if_provider(if_data)
if if_data[:if_provider].to_s =~ /ovs/
if_data[:if_provider] = :ovs
@ -34,7 +69,7 @@ Puppet::Type.type(:l23_stored_config).provide(:ovs_ubuntu, :parent => Puppet::Pr
# Add onboot interfaces
if provider.onboot
if provider.onboot and provider.ipaddr.to_s != 'absent'
header << "auto #{provider.name}"
end
@ -42,15 +77,15 @@ Puppet::Type.type(:l23_stored_config).provide(:ovs_ubuntu, :parent => Puppet::Pr
if provider.if_type.to_s == 'bridge'
header << "allow-ovs #{provider.name}"
props[:bridge_ports] = nil
props[:ovs_type] = 'OVSBridge'
props[:ovs_bridge] = nil
props[:ovs_type] = 'OVSBridge'
props[:bridge] = nil
elsif provider.if_type.to_s == 'bond'
props[:ovs_type] = 'OVSBond'
props[:ovs_bridge] = bridge
props[:bridge] = bridge
else
header << "allow-#{bridge} #{provider.name}"
props[:ovs_type] = 'OVSIntPort'
props[:ovs_bridge] = bridge
props[:ovs_type] = 'OVSIntPort'
props[:bridge] = bridge
end
# Add iface header
header << "iface #{provider.name} inet #{provider.method}"
@ -58,14 +93,5 @@ Puppet::Type.type(:l23_stored_config).provide(:ovs_ubuntu, :parent => Puppet::Pr
return header, props
end
def self.mangle__type(val)
:ethernet
end
def self.unmangle__type(val)
nil
end
end
# vim: set ts=2 sw=2 et :

View File

@ -29,6 +29,7 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
:bond_slaves => 'bond-slaves',
:bond_mode => 'bond-mode',
:bond_miimon => 'bond-miimon',
:bond_lacp => '', # unused for lnx
:bond_lacp_rate => 'bond-lacp-rate',
:bond_xmit_hash_policy => 'bond-xmit-hash-policy'
}
@ -62,6 +63,16 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
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
[
@ -135,18 +146,30 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
val = m[2].strip
case key
# Ubuntu has non-linear config format. Some options should be calculated evristically
when /(auto|allow-\S)/
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
if ooper =~ /allow-(\S)/
hash['if_provider'] = "ovs:#{$1}:#{val}"
else
hash['auto'] = true
hash['if_provider'] ||= "lnx"
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+/)
@ -174,8 +197,16 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
hash['iface'] ||= dirty_iface_name
props = self.mangle_properties(hash)
props.merge!({:family => :inet})
# 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|
@ -196,7 +227,6 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
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] : [])
@ -210,7 +240,6 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
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)
@ -225,7 +254,6 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
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
pairs.delete(in_config_name)
mangle_method_name="mangle__#{type_name}"
if self.respond_to?(mangle_method_name)
rv = self.send(mangle_method_name, val)
@ -337,7 +365,7 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
provider = providers[0]
content, props = iface_file_header(provider)
property_mappings.keys.select{|v| ! properties_fake.include?(v)}.each do |type_name|
property_mappings.keys.select{|v| !(properties_fake.include?(v) or v.empty?)}.each do |type_name|
next if props.has_key? type_name
val = provider.send(type_name)
if val and val.to_s != 'absent'
@ -348,6 +376,8 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
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
@ -390,7 +420,14 @@ class Puppet::Provider::L23_stored_config_ubuntu < Puppet::Provider::L23_stored_
else
rv = val
end
pairs[in_config_name] = rv if ! [nil, :absent].include? rv
# 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

View File

@ -39,6 +39,10 @@ Puppet::Type.newtype(:l23_stored_config) do
newvalues(:ethernet, :bridge, :bond)
end
newproperty(:if_provider) do
desc "Device provider. Service property, shouldn't be setting by puppet"
end
newproperty(:bridge, :array_matching => :all) do
# Array_matching for this property required for very complicated cases
# ex. patchcord for connectind two bridges or bridge and network namesspace
@ -213,6 +217,7 @@ Puppet::Type.newtype(:l23_stored_config) do
newproperty(:bond_mode)
newproperty(:bond_miimon)
newproperty(:bond_lacp)
newproperty(:bond_lacp_rate)
newproperty(:bond_xmit_hash_policy)

View File

@ -105,6 +105,7 @@ define l23network::l2::bond (
mode => $bond_mode,
miimon => $miimon,
lacp_rate => $lacp_rate,
lacp => 'passive',
xmit_hash_policy => $xmit_hash_policy
}
@ -163,6 +164,7 @@ define l23network::l2::bond (
bond_master => undef,
bond_slaves => $interfaces,
bond_miimon => $real_bond_properties[miimon],
bond_lacp => $real_bond_properties[lacp],
bond_lacp_rate => $real_bond_properties[lacp_rate],
bond_xmit_hash_policy => $real_bond_properties[xmit_hash_policy],
delay_while_up => $delay_while_up,

View File

@ -0,0 +1,6 @@
iface bond_lacp inet manual
mtu 9000
ovs_bonds eth2 eth3
ovs_type OVSBond
ovs_bridge br0
ovs_options bond_mode=balance-tcp lacp=active other_config:lacp-time=fast other_config:bond-miimon-interval=50

View File

@ -0,0 +1,3 @@
allow-ovs br9
iface br9 inet manual
ovs_type OVSBridge

View File

@ -0,0 +1,5 @@
allow-br9 ttt0
iface ttt0 inet manual
mtu 6000
ovs_type OVSIntPort
ovs_bridge br9

View File

@ -40,8 +40,15 @@ describe Puppet::Type.type(:l23_stored_config).provider(:lnx_ubuntu) do
providers
end
before(:each) do
if ENV['SPEC_PUPPET_DEBUG']
Puppet::Util::Log.level = :debug
Puppet::Util::Log.newdestination(:console)
end
end
def fixture_path
File.join(PROJECT_ROOT, 'spec', 'fixtures', 'provider', 'l23_stored_config', 'lnx_ubuntu_spec')
File.join(PROJECT_ROOT, 'spec', 'fixtures', 'provider', 'l23_stored_config', 'lnx_ubuntu__spec')
end
def fixture_file(file)

View File

@ -0,0 +1,111 @@
require 'spec_helper'
require 'yaml'
describe Puppet::Type.type(:l23_stored_config).provider(:ovs_ubuntu) do
let(:input_data) do
{
:bond_lacp => {
:name => 'bond_lacp',
:ensure => 'present',
:if_type => 'bond',
:bridge => 'br0',
:mtu => '9000',
:onboot => true,
:method => 'manual',
:bond_mode => 'balance-tcp',
:bond_slaves => ['eth2', 'eth3'],
:bond_miimon => '50',
:bond_lacp_rate => 'fast',
:bond_lacp => 'active',
:provider => "ovs_ubuntu",
},
}
end
let(:resources) do
resources = {}
input_data.each do |name, res|
resources.store name, Puppet::Type.type(:l23_stored_config).new(res)
end
return resources
end
let(:providers) do
providers = {}
resources.each do |name, resource|
provider = resource.provider
if ENV['SPEC_PUPPET_DEBUG']
class << provider
def debug(msg)
puts msg
end
end
end
provider.create
providers.store name, provider
end
providers
end
before(:each) do
if ENV['SPEC_PUPPET_DEBUG']
Puppet::Util::Log.level = :debug
Puppet::Util::Log.newdestination(:console)
end
end
def fixture_path
File.join(PROJECT_ROOT, 'spec', 'fixtures', 'provider', 'l23_stored_config', 'ovs_ubuntu__spec')
end
def fixture_file(file)
File.join(fixture_path, file)
end
def fixture_data(file)
File.read(fixture_file(file))
end
# context "the method property" do
# context 'when dhcp' do
# let(:data) { subject.class.parse_file('eth0', fixture_data('ifcfg-eth0'))[0] }
# it { expect(data[:method]).to eq :dhcp }
# end
# end
context "OVS bond with two interfaces" do
context 'format file' do
subject { providers[:bond_lacp] }
let(:cfg_file) { subject.class.format_file('filepath', [subject]) }
it { expect(cfg_file).not_to match(/auto\s+bond_lacp/) }
it { expect(cfg_file).to match(/iface\s+bond_lacp\s+inet\s+manual/) }
it { expect(cfg_file).to match(/mtu\s+9000/) }
it { expect(cfg_file).to match(/ovs_bonds\s+eth2\s+eth3/) }
it { expect(cfg_file).to match(/ovs_type\s+OVSBond/) }
it { expect(cfg_file).to match(/ovs_bridge\s+br0/) }
it { expect(cfg_file).to match(/ovs_options.+bond_mode=balance-tcp/) }
it { expect(cfg_file).to match(/ovs_options.+other_config:lacp-time=fast/) }
it { expect(cfg_file).to match(/ovs_options.+other_config:bond-miimon-interval=50/) }
it { expect(cfg_file).to match(/ovs_options.+lacp=active/) }
end
context "parse data from fixture" do
let(:res) { subject.class.parse_file('bond_lacp', fixture_data('ifcfg-bond_lacp'))[0] }
it { expect(res[:method]).to eq :manual }
it { expect(res[:mtu]).to eq '9000' }
it { expect(res[:bridge]).to eq 'br0' }
it { expect(res[:if_type].to_s).to eq 'bond' }
it { expect(res[:if_provider].to_s).to eq 'ovs' }
it { expect(res[:bond_mode]).to eq 'balance-tcp' }
it { expect(res[:bond_miimon]).to eq '50' }
it { expect(res[:bond_lacp_rate]).to eq 'fast' }
it { expect(res[:bond_lacp]).to eq 'active' }
it { expect(res[:bond_slaves]).to eq ['eth2', 'eth3'] }
end
end
end

View File

@ -0,0 +1,92 @@
require 'spec_helper'
require 'yaml'
describe Puppet::Type.type(:l23_stored_config).provider(:ovs_ubuntu) do
let(:input_data) do
{
:br9 => {
:name => 'br9',
:ensure => 'present',
:if_type => 'bridge',
:mtu => '9000',
:onboot => true,
:method => 'manual',
:provider => "ovs_ubuntu",
},
}
end
let(:resources) do
resources = {}
input_data.each do |name, res|
resources.store name, Puppet::Type.type(:l23_stored_config).new(res)
end
return resources
end
let(:providers) do
providers = {}
resources.each do |name, resource|
provider = resource.provider
if ENV['SPEC_PUPPET_DEBUG']
class << provider
def debug(msg)
puts msg
end
end
end
provider.create
providers.store name, provider
end
providers
end
before(:each) do
if ENV['SPEC_PUPPET_DEBUG']
Puppet::Util::Log.level = :debug
Puppet::Util::Log.newdestination(:console)
end
end
def fixture_path
File.join(PROJECT_ROOT, 'spec', 'fixtures', 'provider', 'l23_stored_config', 'ovs_ubuntu__spec')
end
def fixture_file(file)
File.join(fixture_path, file)
end
def fixture_data(file)
File.read(fixture_file(file))
end
# context "the method property" do
# context 'when dhcp' do
# let(:data) { subject.class.parse_file('eth0', fixture_data('ifcfg-eth0'))[0] }
# it { expect(data[:method]).to eq :dhcp }
# end
# end
context "standalone OVS bridge" do
context 'format file' do
subject { providers[:br9] }
let(:cfg_file) { subject.class.format_file('filepath', [subject]) }
it { expect(cfg_file).not_to match(/auto\s+br9/) }
it { expect(cfg_file).to match(/allow-ovs\s+br9/) }
it { expect(cfg_file).to match(/iface\s+br9\s+inet\s+manual/) }
it { expect(cfg_file).to match(/ovs_type\s+OVSBridge/) }
end
context "parse data from fixture" do
let(:res) { subject.class.parse_file('br9', fixture_data('ifcfg-bridge'))[0] }
it { expect(res[:method]).to eq :manual }
it { expect(res[:name]).to eq 'br9' }
it { expect(res[:if_type].to_s).to eq 'bridge' }
it { expect(res[:if_provider].to_s).to eq 'ovs' }
end
end
end

View File

@ -0,0 +1,96 @@
require 'spec_helper'
require 'yaml'
describe Puppet::Type.type(:l23_stored_config).provider(:ovs_ubuntu) do
let(:input_data) do
{
:ttt0 => {
:name => 'ttt0',
:ensure => 'present',
:bridge => 'br9',
:mtu => '6000',
:onboot => true,
:method => 'manual',
:provider => "ovs_ubuntu",
},
}
end
let(:resources) do
resources = {}
input_data.each do |name, res|
resources.store name, Puppet::Type.type(:l23_stored_config).new(res)
end
return resources
end
let(:providers) do
providers = {}
resources.each do |name, resource|
provider = resource.provider
if ENV['SPEC_PUPPET_DEBUG']
class << provider
def debug(msg)
puts msg
end
end
end
provider.create
providers.store name, provider
end
providers
end
before(:each) do
if ENV['SPEC_PUPPET_DEBUG']
Puppet::Util::Log.level = :debug
Puppet::Util::Log.newdestination(:console)
end
end
def fixture_path
File.join(PROJECT_ROOT, 'spec', 'fixtures', 'provider', 'l23_stored_config', 'ovs_ubuntu__spec')
end
def fixture_file(file)
File.join(fixture_path, file)
end
def fixture_data(file)
File.read(fixture_file(file))
end
# context "the method property" do
# context 'when dhcp' do
# let(:data) { subject.class.parse_file('eth0', fixture_data('ifcfg-eth0'))[0] }
# it { expect(data[:method]).to eq :dhcp }
# end
# end
context "one OVS port, included to the OVS bridge" do
context 'format file' do
subject { providers[:ttt0] }
let(:cfg_file) { subject.class.format_file('filepath', [subject]) }
it { expect(cfg_file).not_to match(/auto\s+ttt0/) }
it { expect(cfg_file).to match(/allow-br9\s+ttt0/) }
it { expect(cfg_file).to match(/iface\s+ttt0\s+inet\s+manual/) }
it { expect(cfg_file).to match(/mtu\s+6000/) }
it { expect(cfg_file).to match(/ovs_type\s+OVSIntPort/) }
it { expect(cfg_file).to match(/ovs_bridge\s+br9/) }
end
context "parse data from fixture" do
let(:res) { subject.class.parse_file('bond_lacp', fixture_data('ifcfg-port'))[0] }
it { expect(res[:method]).to eq :manual }
it { expect(res[:mtu]).to eq '6000' }
it { expect(res[:bridge]).to eq 'br9' }
it { expect(res[:if_type].to_s).to eq 'ethernet' }
it { expect(res[:if_provider].to_s).to eq 'ovs' }
end
end
end