Refacfored a more suitable ovs_redhat provider

- Added a helper class/library to handle ifcfg content

  - Removed keep_ip and sleep parameters, replaced by
    automatic behaviour

  - No need for a redhat vs_bridge provider

  - Only port/bridge associated with a phyical interface get a
    ifcfg file managed

  - Requires Puppet 2.7.8+
    Not using optional_commands anymore

When the bridge is associated with an active physical interface
  - It will be initially populated from the existing interface
    file, inheriting its parameters

Change-Id: I584fb1442de9a760b3a092f96cbfcbcd6776fdba
This commit is contained in:
Gilles Dubreuil 2014-05-21 10:13:00 +10:00
parent b7a15fc68c
commit 17b62e56e0
5 changed files with 201 additions and 156 deletions

View File

@ -1,51 +0,0 @@
require "puppet"
Base="/etc/sysconfig/network-scripts/ifcfg-"
Puppet::Type.type(:vs_bridge).provide(:ovs_redhat) do
desc "Openvswitch bridge manipulation for RedHat family OSs"
confine :osfamily => :redhat
defaultfor :osfamily => :redhat
optional_commands :vsctl => "/usr/bin/ovs-vsctl",
:ip => "/sbin/ip"
def exists?
vsctl("br-exists", @resource[:name])
rescue Puppet::ExecutionFailure
return false
end
def create
vsctl("add-br", @resource[:name])
ip("link", "set", @resource[:name], "up")
external_ids = @resource[:external_ids] if @resource[:external_ids]
end
def destroy
vsctl("del-br", @resource[:name])
end
def external_ids
result = vsctl("br-get-external-id", @resource[:name])
return result.split("\n").join(",")
end
def external_ids=(value)
old_ids = _split(external_ids)
new_ids = _split(value)
new_ids.each_pair do |k,v|
unless old_ids.has_key?(k)
vsctl("br-set-external-id", @resource[:name], k, v)
end
end
end
private
def _split(string, splitter=",")
return Hash[string.split(splitter).map{|i| i.split("=")}]
end
end

View File

@ -1,105 +1,120 @@
require "puppet"
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'puppetx', 'redhat', 'ifcfg.rb'))
Puppet::Type.type(:vs_port).provide(:ovs_redhat) do
desc "Openvswitch port manipulation for RedHat family OSs"
BASE = '/etc/sysconfig/network-scripts/ifcfg-'
confine :osfamily => :redhat
# When not seedling from interface file
DEFAULT = {
'ONBOOT' => 'yes',
'BOOTPROTO' => 'dhcp',
'PEERDNS' => 'no',
'NM_CONTROLLED' => 'no',
'NOZEROCONF' => 'yes' }
Puppet::Type.type(:vs_port).provide(:ovs_redhat, :parent => :ovs) do
desc 'Openvswitch port manipulation for RedHat OSes family'
confine :osfamily => :redhat
defaultfor :osfamily => :redhat
optional_commands :vsctl => "/usr/bin/ovs-vsctl",
:sleep => "/bin/sleep"
def exists?
vsctl("list-ports", @resource[:bridge]).include? @resource[:interface]
end
commands :ip => 'ip'
commands :ifdown => 'ifdown'
commands :ifup => 'ifup'
commands :vsctl => 'ovs-vsctl'
def create
if @resource[:keep_ip]
create_bridge_file
create_physical_interface_file
activate_port
unless vsctl('list-ports',
@resource[:bridge]).include? @resource[:interface]
super
end
if interface_physical?
template = DEFAULT
extras = nil
if link?
extras = dynamic_default if dynamic?
if File.exist?(BASE + @resource[:interface])
template = from_str(File.read(BASE + @resource[:interface]))
end
end
port = IFCFG::Port.new(@resource[:interface], @resource[:bridge])
port.save(BASE + @resource[:interface])
bridge = IFCFG::Bridge.new(@resource[:bridge], template)
bridge.set(extras) if extras
bridge.save(BASE + @resource[:bridge])
ifdown(@resource[:bridge])
ifdown(@resource[:interface])
ifup(@resource[:interface])
ifup(@resource[:bridge])
end
end
def exists?
if interface_physical?
super &&
IFCFG::OVS.exists?(@resource[:interface]) &&
IFCFG::OVS.exists?(@resource[:bridge])
else
vsctl("add-port", @resource[:bridge], @resource[:interface])
super
end
end
def destroy
vsctl("del-port", @resource[:bridge], @resource[:interface])
if interface_physical?
ifdown(@resource[:bridge])
ifdown(@resource[:interface])
IFCFG::OVS.remove(@resource[:interface])
IFCFG::OVS.remove(@resource[:bridge])
end
super
end
private
def activate_port
atomic_operation="ifdown #{@resource[:interface]};
ovs-vsctl add-port #{@resource[:bridge]} #{@resource[:interface]};
ifup #{@resource[:interface]};
ifup #{@resource[:bridge]}"
system(atomic_operation)
sleep(@resource[:sleep]) if @resource[:sleep]
end
def create_physical_interface_file
file = File.open(Base + @resource[:interface], 'w+')
file << "DEVICE=#{@resource[:interface]}\n"
file << "DEVICETYPE=ovs\n"
file << "TYPE=OVSPort\n"
file << "BOOTPROTO=none\n"
file << "OVS_BRIDGE=#{@resource[:bridge]}\n"
file << "ONBOOT=yes\n"
file.close
def dynamic?
device = ''
device = ip('addr', 'show', @resource[:interface])
return device =~ /dynamic/ ? true : false
end
def search(file_name, value)
File.open(file_name) { |file|
file.each_line { |line|
match = value.match(line)
return match[0] if match
}
}
end
def create_bridge_file
bridge_file = File.open(Base + @resource[:bridge], 'w+')
interface_file_name = Base + @resource[:interface]
# Ultimately this to go to vs_bridge
bridge_file << "DEVICE=#{@resource[:bridge]}\n"
bridge_file << "TYPE=OVSBridge\n"
bridge_file << "DEVICETYPE=ovs\n"
bridge_file << "ONBOOT=yes\n"
# End ultimately
case search(interface_file_name, /bootproto=.*/i)
when /dhcp/
bridge_file << "OVSBOOTPROTO=dhcp\n"
bridge_file << "OVSDHCPINTERFACES=#{@resource[:interface]}\n"
when /static/, /none/
bridge_file << "OVSBOOTPROTO=static\n"
ipaddr = search(interface_file_name, /ipaddr=.*/i)
if ipaddr.class == String
bridge_file << ipaddr + "\n"
else
raise RuntimeError, 'Undefined IP address'
end
mask = search(interface_file_name, /(prefix|netmask)=.*/i)
if mask.class == String
bridge_file << mask + "\n"
else
raise RuntimeError, 'Undefined netmask or prefix'
end
else
raise RuntimeError, 'Undefined boot protocol'
def link?
if File.read("/sys/class/net/#{@resource[:interface]}/operstate") =~ /up/
return true
else
return false
end
# The idea here to have a fixed MAC address
datapath_id = vsctl("get", "bridge", @resource[:bridge], 'datapath_id')
bridge_mac_address = datapath_id[-14..-3].scan(/.{1,2}/).join(':') if datapath_id
if bridge_mac_address
bridge_file << "OVS_EXTRA=\"set bridge #{@resource[:bridge]} other-config:hwaddr=#{bridge_mac_address}\"\n"
end
bridge_file.close
rescue Errno::ENOENT
return false
end
end
def dynamic_default
list = { 'OVSDHCPINTERFACES' => @resource[:interface] }
# Persistent MAC address taken from interface
bridge_mac_address = File.read("/sys/class/net/#{@resource[:interface]}/address").chomp
if bridge_mac_address != ''
list.merge!({ 'OVS_EXTRA' =>
"\"set bridge #{@resource[:bridge]} other-config:hwaddr=#{bridge_mac_address}\"" })
end
list
end
def interface_physical?
# OVS ports don't have entries in /sys/class/net
# Alias interfaces (ethX:Y) must use ethX entries
interface = @resource[:interface].sub(/:\d/, '')
! Dir["/sys/class/net/#{interface}"].empty?
end
def from_str(data)
items = {}
data.each_line do |line|
if m = line.match(/^(.*)=(.*)$/)
items.merge!(m[1] => m[2])
end
end
items
end
end

View File

@ -0,0 +1,17 @@
Puppet::Type.type(:vs_port).provide(:ovs_redhat_el6, :parent => :ovs_redhat) do
desc 'Openvswitch port manipulation for RedHat OSes family'
confine :osfamily => :redhat, :operatingsystemmajrelease => 6
defaultfor :osfamily => :redhat, :operatingsystemmajrelease => 6
private
def dynamic?
# iproute doesn't behave as expected on rhel6 for dynamic interfaces
if File.read(BASE + @resource[:interface]) =~ /^BOOTPROTO=['"]?dhcp['"]?$/
return true
else
return false
end
end
end

View File

@ -16,7 +16,7 @@ Puppet::Type.newtype(:vs_port) do
end
newparam(:bridge) do
desc "What bridge to use"
desc 'The bridge to attach to'
validate do |value|
if !value.is_a?(String)
@ -25,24 +25,6 @@ Puppet::Type.newtype(:vs_port) do
end
end
newparam(:keep_ip) do
desc "True: keep physical interface's details and assign them to the bridge"
defaultto false
end
newparam(:sleep) do
desc "Waiting time, in seconds (0 by default), for network to sync after activating port, used with keep_ip only"
defaultto '0'
validate do |value|
if value.to_i.class != Fixnum || value.to_i < 0
raise ArgumentError, "sleep requires a positive integer"
end
end
end
autorequire(:vs_bridge) do
self[:bridge] if self[:bridge]
end

View File

@ -0,0 +1,82 @@
module IFCFG
class OVS
attr_reader :ifcfg
def self.exists?(name)
File.exist?(BASE + name)
end
def self.remove(name)
File.delete(BASE + name)
rescue Errno::ENOENT
end
def initialize(name, seed=nil)
@name = name
@ifcfg = {}
set(seed)
set_key('DEVICE', @name)
set_key('DEVICETYPE', 'ovs')
replace_key('BOOTPROTO', 'OVSBOOTPROTO') if self.class == IFCFG::Bridge
end
def del_key(key)
@ifcfg.delete(key)
end
def key?(key)
@ifcfg.has_key?(key)
end
def key(key)
@ifcfg.has_key?(key)
end
def replace_key(key, new_key)
value = @ifcfg[key]
@ifcfg.delete(key)
set_key(new_key, value)
end
def set(list)
if list != nil && list.class == Hash
list.each { |key, value| set_key(key, value) }
end
end
def set_key(key, value)
@ifcfg.delete_if { |k, v| k == key } if self.key?(key)
@ifcfg.merge!({key => value })
end
def to_s
str = ''
@ifcfg.each { |x, y|
str << "#{x}=#{y}\n"
}
str
end
def save(filename)
File.open(filename, 'w') { |file| file << self.to_s }
end
end
class Bridge < OVS
def initialize(name, template=nil)
super(name, template)
set_key('TYPE', 'OVSBridge')
del_key('HWADDR')
end
end
class Port < OVS
def initialize(name, bridge)
super(name)
set_key('TYPE', 'OVSPort')
set_key('OVS_BRIDGE', bridge)
set_key('ONBOOT', 'yes')
set_key('BOOTPROTO', 'none')
end
end
end