Blob Blame History Raw
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