From 87e21ac6b3a62df614c082f973f817f5ef5c6e61 Mon Sep 17 00:00:00 2001
From: Gilles Dubreuil <gilles@redhat.com>
Date: Wed, 21 May 2014 10:13:00 +1000
Subject: [PATCH] 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
---
lib/puppet/provider/vs_bridge/ovs_redhat.rb | 51 --------
lib/puppet/provider/vs_port/ovs_redhat.rb | 179 ++++++++++++++------------
lib/puppet/provider/vs_port/ovs_redhat_el6.rb | 17 +++
lib/puppet/type/vs_port.rb | 20 +--
lib/puppetx/redhat/ifcfg.rb | 82 ++++++++++++
5 files changed, 197 insertions(+), 152 deletions(-)
delete mode 100644 lib/puppet/provider/vs_bridge/ovs_redhat.rb
create mode 100644 lib/puppet/provider/vs_port/ovs_redhat_el6.rb
create mode 100644 lib/puppetx/redhat/ifcfg.rb
diff --git a/lib/puppet/provider/vs_bridge/ovs_redhat.rb b/lib/puppet/provider/vs_bridge/ovs_redhat.rb
deleted file mode 100644
index 5495d12..0000000
--- a/lib/puppet/provider/vs_bridge/ovs_redhat.rb
+++ /dev/null
@@ -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
diff --git a/lib/puppet/provider/vs_port/ovs_redhat.rb b/lib/puppet/provider/vs_port/ovs_redhat.rb
index 6d43797..f2cf88d 100644
--- a/lib/puppet/provider/vs_port/ovs_redhat.rb
+++ b/lib/puppet/provider/vs_port/ovs_redhat.rb
@@ -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"
+ commands :ip => 'ip'
+ commands :ifdown => 'ifdown'
+ commands :ifup => 'ifup'
+ commands :vsctl => 'ovs-vsctl'
- def exists?
- vsctl("list-ports", @resource[:bridge]).include? @resource[:interface]
+ def create
+ 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 create
- if @resource[:keep_ip]
- create_bridge_file
- create_physical_interface_file
- activate_port
+ 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
- }
- }
+ def link?
+ if File.read("/sys/class/net/#{@resource[:interface]}/operstate") =~ /up/
+ return true
+ else
+ return false
+ end
+ rescue Errno::ENOENT
+ return false
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 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
-
- # 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"
+ 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
- bridge_file.close
+ items
end
-end
\ No newline at end of file
+end
diff --git a/lib/puppet/provider/vs_port/ovs_redhat_el6.rb b/lib/puppet/provider/vs_port/ovs_redhat_el6.rb
new file mode 100644
index 0000000..c50a9b9
--- /dev/null
+++ b/lib/puppet/provider/vs_port/ovs_redhat_el6.rb
@@ -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
diff --git a/lib/puppet/type/vs_port.rb b/lib/puppet/type/vs_port.rb
index df4705e..4527bd9 100644
--- a/lib/puppet/type/vs_port.rb
+++ b/lib/puppet/type/vs_port.rb
@@ -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
diff --git a/lib/puppetx/redhat/ifcfg.rb b/lib/puppetx/redhat/ifcfg.rb
new file mode 100644
index 0000000..262e6e1
--- /dev/null
+++ b/lib/puppetx/redhat/ifcfg.rb
@@ -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
--
1.9.3