Suppport for bonded interfaces

This separates the interface parameter from the port name. The port name
is the new namevar and interface can be an array of interfaces for
bonding. The default value ensures that this is mostly backwards compatible.
The only incompatibility is if the interface parameter was explicitly
set. In this case the new port parameter has to be used instead.

The bonding configuration can be set with the bond_mode, lacp and
lacp_time parameters.

Change-Id: I0364ce46dce4b87a457d2265518fa968a48e3529
This commit is contained in:
Gaudenz Steinlin 2014-09-29 18:26:18 +02:00
parent 17b62e56e0
commit 035ef34f7c
3 changed files with 167 additions and 5 deletions

View File

@ -1,21 +1,117 @@
require 'puppet'
UUID_RE = /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/
Puppet::Type.type(:vs_port).provide(:ovs) do
desc 'Openvswitch port manipulation'
has_feature :bonding
commands :vsctl => 'ovs-vsctl'
def exists?
vsctl('list-ports', @resource[:bridge]).include? @resource[:interface]
vsctl('list-ports', @resource[:bridge]).include? @resource[:port]
rescue Puppet::ExecutionFailure => e
return false
end
def create
vsctl('add-port', @resource[:bridge], @resource[:interface])
# create with first interface, other interfaces will be added later when synchronizing properties
vsctl('--', '--id=@iface0', 'create', 'Interface', "name=#{@resource[:interface][0]}", '--', 'add-port', @resource[:bridge], @resource[:port], 'interfaces=@iface0')
# synchronize properties
# Only sync those properties actually supported by the provider. This
# allows this provider to be used as a base class for providers not
# supporting all properties.
sync_properties = []
if self.bonding?
sync_properties += [:interface,
:bond_mode,
:lacp,
:lacp_time,
]
end
for prop_name in sync_properties
property = @resource.property(prop_name)
property.sync unless property.safe_insync?(property.retrieve)
end
end
def destroy
vsctl('del-port', @resource[:bridge], @resource[:interface])
vsctl('del-port', @resource[:bridge], @resource[:port])
end
def interface
get_port_interface_column('name')
end
def interface=(value)
# find interfaces we want to keep on the port
keep = @resource.property(:interface).retrieve() & value
keep_uids = keep.map { |iface| vsctl('get', 'Interface', iface, '_uuid').strip }
new = value - keep
args = ['--'] + new.each_with_index.map { |iface, i| ["--id=@#{i+1}", 'create', 'Interface', "name=#{iface}", '--'] }
ifaces = (1..new.length).map { |i| "@#{i}" } + keep_uids
args += ['set', 'Port', @resource[:port], "interfaces=#{ifaces.join(',')}"]
vsctl(*args)
end
def bond_mode
get_port_column('bond_mode')
end
def bond_mode=(value)
set_port_column('bond_mode', value)
end
def lacp
get_port_column('lacp')
end
def lacp=(value)
set_port_column('lacp', value)
end
def lacp_time
get_port_column('other_config:lacp-time')
end
def lacp_time=(value)
set_port_column('other_config:lacp-time', value)
end
private
def port_column_command(command, column, value=nil)
if value
vsctl(command, 'Port', @resource[:port], column, value)
else
vsctl('--if-exists', command, 'Port', @resource[:port], column)
end
end
def get_port_column(column)
value = port_column_command('get', column).strip
if value == '[]' then '' else value end
end
def set_port_column(column, value)
if ! value or value.empty?
# columns with maps need special handling, single map entries
# can be removed with the remove command
column, key = column.split(':')
if ! key
port_column_command('clear', column)
else
port_column_command('remove', [column, key])
end
else
port_column_command('set', "#{column}=#{value}")
end
end
def get_port_interface_column(column)
uuids = get_port_column('interfaces').scan(UUID_RE)
uuids.map!{|id| vsctl('get', 'Interface', id, column).strip.tr('"', '')}
end
end

View File

@ -21,6 +21,15 @@ Puppet::Type.type(:vs_port).provide(:ovs_redhat, :parent => :ovs) do
commands :ifup => 'ifup'
commands :vsctl => 'ovs-vsctl'
def initialize(value={})
super(value)
# Set interface property although it's not really
# supported on this provider. This ensures that all
# methodes inherited from the ovs provider work as
# expected.
@resource[:interface] = @resource[:port]
end
def create
unless vsctl('list-ports',
@resource[:bridge]).include? @resource[:interface]

View File

@ -3,16 +3,35 @@ require 'puppet'
Puppet::Type.newtype(:vs_port) do
desc 'A Virtual Switch Port'
feature :bonding, "The provider supports bonded interfaces"
ensurable
newparam(:interface, :namevar => true) do
desc 'The interface to attach to the bridge'
newparam(:port, :namevar => true) do
desc 'Name of the port.'
validate do |value|
if !value.is_a?(String)
raise ArgumentError, "Invalid port name #{value}. Requires a String, not a #{value.class}"
end
end
end
newproperty(:interface, :array_matching => :all, :required_features => :bonding) do
desc 'The interfaces to attach to the bridge. Defaults to the interface with the same name as the port.'
defaultto { @resource[:port] }
validate do |value|
if !value.is_a?(String)
raise ArgumentError, "Invalid interface #{value}. Requires a String, not a #{value.class}"
end
end
# order of interfaces does not matter
def insync?(is)
is.sort == should.sort
end
end
newparam(:bridge) do
@ -25,6 +44,44 @@ Puppet::Type.newtype(:vs_port) do
end
end
newproperty(:bond_mode, :required_features => :bonding) do
desc "Interface bonding mode for this port.
Possible values are 'active-backup', 'balance-tcp' or 'balance-slb'.
By default no bonding mode is set (bond_mode='')."
defaultto ""
newvalues(:"active-backup", :"balance-tcp", :"balance-slb", "")
end
newproperty(:lacp, :required_features => :bonding) do
desc "LACP configuration for this port.
Possible values are 'active', 'passive' or 'off'. The default is
'off'."
defaultto :off
newvalues(:active, :passive, :off)
end
newproperty(:lacp_time, :required_features => :bonding) do
desc "The LACP timing which should be used on this Port.
Possible values are 'slow' and 'fast'. The default is 'slow'.
When configured to be fast LACP heartbeats are requested at a rate of
once per second causing connectivity problems to be detected more quickly.
In slow mode, heartbeats are requested at a rate of once every 30 seconds."
# Default to the empty string which is equivalent to slow as this is also
# the OVS default. This avoids setting a useless property on non bonded ports.
defaultto ""
newvalues(:fast, :slow, "")
end
autorequire(:vs_bridge) do
self[:bridge] if self[:bridge]
end