crowbar/crowbar-core

View on GitHub
chef/cookbooks/network/recipes/default.rb

Summary

Maintainability
C
1 day
Test Coverage
# Copyright 2013, Dell
# Copyright 2012, SUSE Linux Products GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

return if node[:platform_family] == "windows"

dirty = false

# Make sure packages we need will be present
node[:network][:base_pkgs].each do |pkg|
  p = package pkg do
    action :nothing
  end
  p.run_action :install
end

if node[:network][:needs_openvswitch]
  node[:network][:ovs_pkgs].each do |pkg|
    p = package pkg do
      action :nothing
    end
    p.run_action :install
  end

  unless ::File.exist?("/sys/module/#{node[:network][:ovs_module]}")
    ::Kernel.system("modprobe #{node[:network][:ovs_module]}")
  end

  s = service node[:network][:ovs_service] do
    action [:nothing]
  end
  s.run_action :enable
  s.run_action :start

  # Cleanup on SLE12. Disable (NOT stop) old sysvinit service for ovs to avoid
  # issues (https://bugzilla.suse.com/show_bug.cgi?id=935912). We use the
  # (differently named) systemd unit on newer suse platforms now.
  if node[:platform_family] == "suse" && node[:platform_version].to_f >= 12.0
    s = service "openvswitch-switch" do
      action [:nothing]
    end
    s.run_action :disable
  end

  directory "/etc/systemd/system/openvswitch.service.d" do
    owner "root"
    group "root"
    mode 0o755
    action :create
  end

  template "/etc/systemd/system/openvswitch.service.d/99-wickedd.conf" do
    source "openvswitch.service-wickedd.conf.erb"
    owner "root"
    group "root"
    mode 0o644
  end

  bash "reload systemd for openvswitch.service extension" do
    code "systemctl daemon-reload"
    action :nothing
    subscribes :run, "template[/etc/systemd/system/openvswitch.service.d/99-wickedd.conf]", :immediate
  end

end

require "fileutils"

if node[:platform] == "ubuntu"
  if ::File.exist?("/etc/init/network-interface.conf")
    # Make upstart stop trying to dynamically manage interfaces.
    ::File.unlink("/etc/init/network-interface.conf")
    ::Kernel.system("killall -HUP init")
  end

  # Stop udev from jacking up our vlans and bridges as we create them.
  ["40-bridge-network-interface.rules","40-vlan-network-interface.rules"].each do |rule|
    next if ::File.exist?("/etc/udev/rules.d/#{rule}")
    next unless ::File.exist?("/lib/udev/rules.d/#{rule}")
    ::Kernel.system("echo 'ACTION==\"add\", SUBSYSTEM==\"net\", RUN+=\"/bin/true\"' >/etc/udev/rules.d/#{rule}")
  end
end

# Make sure netfilter is enabled for bridges
cookbook_file "modprobe-bridge.conf" do
  source "modprobe-bridge.conf"
  path "/etc/modprobe.d/10-bridge-netfilter.conf"
  mode "0644"
end
# Overrides 50-default.conf bsc#1115020
cookbook_file "51-bridge-netfilter.conf" do
  source "51-bridge-netfilter.conf"
  path "/usr/lib/sysctl.d/51-bridge-netfilter.conf"
  mode "0644"
end

# If the module is already loaded when we create the modprobe config file,
# then we need to act and manually change the settings
execute "enable netfilter for bridges" do
  command <<-EOF
    echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables;
    echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables;
    echo 1 > /proc/sys/net/bridge/bridge-nf-call-arptables
  EOF
  only_if "lsmod | grep -q '^bridge '"
  action :nothing
  subscribes :run, resources(cookbook_file: "modprobe-bridge.conf"), :delayed
end

# Read this before doing any network reconfigurations, as these calls require
# network access, which we might lack for some time during the run of this
# recipe. (Especially in the compile phase)
provisioner_config = Barclamp::Config.load("core", "provisioner")
provisioner_address = provisioner_config["server"]

conduit_map = Barclamp::Inventory.build_node_map(node)
Chef::Log.debug("Conduit mapping for this node:  #{conduit_map.inspect}")
route_pref = 10000
ifs = Mash.new
old_ifs = node["crowbar_wall"]["network"]["interfaces"] || Mash.new rescue Mash.new
if_mapping = Mash.new
addr_mapping = Mash.new
default_route = {}
# flag to track if we need to enable wicked-nanny on SLES12
ovs_bridge_created = false

# dhclient running?  Not for long.
::Kernel.system("killall -w -q -r '^dhclient'")

# Silly little helper for sorting Crowbar networks.
# Networks that use vlans and bridges will be handled later
def net_weight(net)
  res = 0
  if net.use_vlan then res += 1 end
  if net.add_bridge then res += 1 end
  res
end

def kill_nic_files(nic)
  case node[:platform_family]
  when "rhel"
    # Redhat and Centos have lots of small files definining interfaces.
    # Delete the ones we no longer care about here.
    if ::File.exist?("/etc/sysconfig/network-scripts/ifcfg-#{nic.name}")
      ::File.delete("/etc/sysconfig/network-scripts/ifcfg-#{nic.name}")
    end
  when "suse"
    # SuSE also has lots of small files, but in slightly different locations.
    [
      "/etc/sysconfig/network/ifcfg-#{nic.name}",
      "/etc/sysconfig/network/ifroute-#{nic.name}",
      "/etc/wicked/scripts/#{nic.name}-pre-up"
    ].each do |nicfile|
      if ::File.exist?(nicfile)
        ::File.delete(nicfile)
        Chef::Log.info("Deleting #{nicfile}")
      end
    end
  end
end

def kill_nic(nic)
  raise "Cannot kill #{nic.name} because it does not exist!" unless Nic.exists?(nic.name)

  # Ignore loopback interfaces for now.
  return if nic.loopback?

  Chef::Log.info("Interface #{nic.name} is no longer being used, deconfiguring it.")
  nic.destroy
  ::Kernel.system("wicked ifdown #{nic.name}")

  kill_nic_files(nic)
end

sorted_networks = Barclamp::Inventory.list_networks(node).sort do |a, b|
  net_weight(a) <=> net_weight(b)
end

# Dynamically create our new local interfaces.
sorted_networks.each do |network|
  next if network.name == "bmc"

  net_ifs = Array.new
  addr = if network.address
    IP.coerce("#{network.address}/#{network.netmask}")
  else
    nil
  end
  unless conduit_map.key?(network.conduit)
    raise "Conduit \"#{network.conduit}\" does not exist in conduit mapping!"
  end
  base_ifs = conduit_map[network.conduit]["if_list"]
  # Error out if we were handed an invalid conduit mapping.
  unless base_ifs.all? { |i| i.is_a?(String) && ::Nic.exists?(i) }
    raise ::ArgumentError.new("Conduit mapping \"#{network.conduit}\" for network \"#{network.name}\" is not sane: #{base_ifs.inspect}")
  end
  base_ifs = base_ifs.map { |i| ::Nic.new(i) }
  Chef::Log.info("Using base interfaces #{base_ifs.map(&:name).inspect} for network #{network.name}")
  base_ifs.each do |i|
    ifs[i.name] ||= Hash.new
    ifs[i.name]["addresses"] ||= Array.new
    ifs[i.name]["type"] = "physical"
  end
  case base_ifs.length
  when 0
    Chef::Log.fatal("Conduit #{network.conduit} does not have any nics. Your config is invalid.")
    raise ::RangeError.new("Invalid conduit mapping #{conduit_map.inspect}")
  when 1
    Chef::Log.info("Using interface #{base_ifs[0]} for network #{network.name}")
    our_iface = base_ifs[0]
  else
    # We want a bond.  Figure out what mode it should be.  Default to 5
    team_mode = conduit_map[network.conduit]["team_mode"] ||
      (node["network"]["teaming"] && node["network"]["teaming"]["mode"]) || 5
    miimon = conduit_map[network.conduit]["team_miimon"] ||
      (node["network"]["teaming"] &&
       node["network"]["teaming"]["miimon"]) || 100
    xmit_hash_policy = conduit_map[network.conduit]["team_xmit_hash_policy"] ||
      (node["network"]["teaming"] &&
       node["network"]["teaming"]["xmit_hash_policy"]) || "layer2"
    # See if a bond that matches our specifications has already been created,
    # or if there is an empty bond lying around.
    bond = Nic::Bond.find(base_ifs)
    if bond
      Chef::Log.info("Using bond #{bond.name} for network #{network.name}")
      bond.mode = team_mode if bond.mode != team_mode
    else
      # Error out if any of the interfaces already used elsewhere.
      # FIXME: Hitting this means that we'd need to reconfigure all the upper layers
      # of the interface:
      # - unconfigure existing VLANs on top of this
      # - tear down any bond that this interface is currently enslaved in
      # - unplug for any existing (ovs-)bridge (would likely involve destroying
      #   that bridge as well)
      # later steps in the recipe would bring back any of the upper layers
      base_ifs.each do |i|
        if i.children.any?
          raise "Base interface '#{i.name}' is already used by VLAN(s) " \
            "'#{i.children.map(&:name).inspect}' refusing to add it to a bond."
        end
        if i.master
          raise "Base interface '#{i.name}' is already used by #{i.master.class} " \
            "'#{i.master.name}' refusing to add it to a new bond."
        end
      end

      existing_bond_names = Nic.nics.select{ |i| Nic::bond?(i) }.map{ |i| i.name }
      bond_names = (0..existing_bond_names.length).to_a.map{ |i| "bond#{i}" }
      new_bond_name = (bond_names - existing_bond_names).first

      bond = Nic::Bond.create(new_bond_name, team_mode, miimon, xmit_hash_policy)
      Chef::Log.info("Creating bond #{bond.name} for network #{network.name}")
    end
    ifs[bond.name] ||= Hash.new
    ifs[bond.name]["addresses"] ||= Array.new
    ifs[bond.name]["slaves"] = Array.new
    base_ifs.each do |i|
      # If the slave isn't already a member of this bond, it may be configured
      # with an IP or DHCP, and we don't want wicked to re-apply it when the
      # interface is brought back up.
      unless bond.slaves.include? i
        ::Kernel.system("wicked ifdown #{i.name}")
        # Delete the associated config file as well. Otherwise, in cases where
        # this recipe fails (for whatever reason) before writing the new
        # configuration files, a "network restart", as e.g. issued by crowbar_join
        # will endup with an inconsistent configuration.
        kill_nic_files(i)
      end
      bond.add_slave i
      ifs[bond.name]["slaves"] << i.name
      ifs[i.name]["slave"] = true
      ifs[i.name]["master"] = bond.name
    end
    ifs[bond.name]["mode"] = team_mode
    ifs[bond.name]["type"] = "bond"
    ifs[bond.name]["miimon"] = miimon
    ifs[bond.name]["xmit_hash_policy"] = xmit_hash_policy
    # Save miimon and xmit_hash_policy to the NIC object if they are different,
    # since writing them causes the kernel to emit messages about setting them,
    # and we want the information saved so it makes it into the configuration
    # files.
    bond.miimon = miimon unless bond.miimon == miimon
    unless bond.xmit_hash_policy == xmit_hash_policy
      bond.xmit_hash_policy = xmit_hash_policy
    end
    our_iface = bond
    node.set["crowbar"]["bond_list"] ||= {}
    if node["crowbar"]["bond_list"][bond.name] != ifs[bond.name]["slaves"]
      node.set["crowbar"]["bond_list"][bond.name] = ifs[bond.name]["slaves"]
      dirty = true
    end
  end
  net_ifs << our_iface.name
  # If we want a vlan interface, create one on top of the base physical
  # interface and/or bond that we already have
  if network.use_vlan
    vlan = "#{our_iface.name}.#{network.vlan}"
    if Nic.exists?(vlan) && Nic.vlan?(vlan)
      Chef::Log.info("Using vlan #{vlan} for network #{network.name}")
      our_iface = Nic.new vlan
      have_vlan_iface = true
    else
      have_vlan_iface = false
    end
    # Destroy any vlan interfaces for this vlan that might
    # already exist, but with a different naming scheme
    Nic.nics.each do |n|
      next unless n.kind_of?(Nic::Vlan)
      next if have_vlan_iface && n == our_iface
      next unless n.parent == our_iface.name
      next unless n.vlan == network.vlan
      kill_nic(n)
    end
    unless have_vlan_iface
      Chef::Log.info("Creating vlan #{vlan} for network #{network.name}")
      our_iface = Nic::Vlan.create(our_iface, network.vlan)
    end
    ifs[our_iface.name] ||= Hash.new
    ifs[our_iface.name]["addresses"] ||= Array.new
    ifs[our_iface.name]["type"] = "vlan"
    ifs[our_iface.name]["vlan"] = our_iface.vlan
    ifs[our_iface.name]["parent"] = our_iface.parents[0].name
    net_ifs << our_iface.name
  end
  # Ditto for a bridge.
  if network.add_bridge
    bridge = if our_iface.kind_of?(Nic::Vlan)
      "br#{our_iface.vlan}"
    else
      "br-#{network.name}"
    end
    br = if Nic.exists?(bridge) && Nic.bridge?(bridge)
      Chef::Log.info("Using bridge #{bridge} for network #{network.name}")
      Nic.new bridge
    else
      Chef::Log.info("Creating bridge #{bridge} for network #{network.name}")
      Nic::Bridge.create(bridge)
    end
    ifs[br.name] ||= Hash.new
    ifs[br.name]["addresses"] ||= Array.new
    ifs[our_iface.name]["slave"] = true
    ifs[our_iface.name]["master"] = br.name
    br.add_slave our_iface
    ifs[br.name]["slaves"] = [our_iface.name]
    ifs[br.name]["type"] = "bridge"
    our_iface = br
    net_ifs << our_iface.name
  end
  if network.add_ovs_bridge
    bridge = network.bridge_name || "br-#{network.name}"

    # Check if our current base interface (bond, vlan or physical) is already
    # attached to another OVS bridge. Take care about that appropriately.
    if our_iface.ovs_master && our_iface.ovs_master.name != bridge
      old_ovs = our_iface.ovs_master

      # We are already attached to an ovs bridge, check whether that attachment
      # was already verified (or created) during this chef-client run, by looking
      # through the list of interfaces that we already touched ('ifs'). If it is,
      # find out which crowbar network requires that attachement and error out
      # with a meaningful error message.
      if ifs.key? old_ovs.name
        other_net = sorted_networks.find do |n|
          n.bridge_name == old_ovs.name
        end
        other_net ||= sorted_networks.find do |n|
          old_ovs.name == "br-#{n.name}"
        end
        raise "The current conduit mapping for network '#{network.name}' requires " \
          "interface '#{our_iface.name}' to be attached to ovs-brige '#{bridge}'. " \
          "But network '#{other_net.name}' which was already set up, requires " \
          "the same interface to be attached to ovs-bridge '#{old_ovs.name}'. " \
          "Refusing to reconfigure. There seems to be a conflict in the conduit" \
          "mappings for the networks '#{other_net.name}' and '#{network.name}'."
      end

      # We didn't create the attachement during the current run of chef-client.
      # Tear down the attachement (and bridge configuration) to be able to attach
      # the current interface to the new bridge.
      Chef::Log.warn("Current conduit mapping for network '#{network.name}' requires " \
                     "ovs-brige '#{bridge}' to be attached to device '#{our_iface.name}' " \
                     "but the device is already attached to '#{old_ovs.name}'.")
      Chef::Log.warn("Unplugging '#{our_iface.name}' from '#{old_ovs.name}' and " \
                     "taking the bridge down for reconfiguration to reduce the " \
                     "risk of creating a network loop.")
      old_ovs.unplug(our_iface.name)
      ::Kernel.system("wicked ifdown #{old_ovs.name}")
      kill_nic_files old_ovs
    end

    # This flag is used later to enable wicked-nanny (on SUSE platforms)
    ovs_bridge_created = true

    if Nic.exists?(bridge) && Nic.ovs_bridge?(bridge)
      ovs = Nic.new bridge
      ovs_slaves = ovs.slaves

      # Find out if the ovs bridge for the current network is already attached to
      # any other physical, vlan or bond interface than what the current configuration
      # requires. If it is, tear down the bridge and slave devices for reconfig to
      # reduce possible risk creating network loops.
      non_virtual_slaves = ovs_slaves.find_all do |slave|
        slave.name != our_iface.name && Nic.base_interface?(slave)
      end
      unless non_virtual_slaves.nil? || non_virtual_slaves.empty?
        Chef::Log.warn("Current conduit mapping for network '#{network.name}' requires " \
                       "ovs-brige '#{bridge}' to be attached to device '#{our_iface.name}' " \
                       "but the bridge is already attached to '[#{non_virtual_slaves.join " "}]'.")
        Chef::Log.warn("Taking the bridge down for reconfiguration to reduce risk of " \
                       "creating a network loop.")
        Chef::Log.info("Deleting OVS bridge #{bridge}")
        ::Kernel.system("wicked ifdown #{ovs.name}")
        kill_nic_files ovs
        non_virtual_slaves.each do |i|
          ::Kernel.system("wicked ifdown #{i.name}")
          kill_nic_files i
        end
      end
    end

    br = if Nic.exists?(bridge) && Nic.ovs_bridge?(bridge)
      Chef::Log.info("Using OVS bridge #{bridge} for network #{network.name}")
      Nic.new bridge
    else
      Chef::Log.info("Creating OVS bridge #{bridge} for network #{network.name}")
      Nic::OvsBridge.create(bridge)
    end
    ifs[br.name] ||= Hash.new
    ifs[br.name]["addresses"] ||= Array.new
    ifs[our_iface.name]["slave"] = true
    ifs[our_iface.name]["master"] = br.name
    unless our_iface.ovs_master && our_iface.ovs_master.name == br.name
      # FIXME: Workaround for https://bugzilla.suse.com/show_bug.cgi?id=945219
      # Find vlan interface on top of 'our_iface' that are plugged into other
      # ovs bridges. Replug them.
      our_kids = our_iface.children.find_all { |c| Nic.vlan?(c) }
      replug_interface = {}
      our_kids.each do |k|
        ovs_master = k.ovs_master
        unless ovs_master.nil?
          Chef::Log.warn("removing #{k.name} from #{ovs_master.name} (workaround bnc#945219 start)")
          ovs_master.unplug(k.name)
          replug_interface[ovs_master.name] = k.name
        end
      end
      br.add_slave our_iface
      replug_interface.each do |bridgename, interface|
        replug_br = Nic.new bridgename
        unless replug_br.nil?
          Chef::Log.warn("adding #{interface} to #{bridgename} (workaround bnc#945219 end)")
          replug_br.plug(interface)
        end
      end
    end
    ifs[br.name]["slaves"] = [our_iface.name]
    ifs[br.name]["type"] = "ovs_bridge"
    our_iface = br
    net_ifs << our_iface.name
  end

  unless network.ovs_forward_bpdu.nil?
    Chef::Log.info("OVS BPDU forwarding set to #{network.ovs_forward_bpdu}.")
    ifs[our_iface.name]["ovs_forward_bpdu"] = network.ovs_forward_bpdu
  end

  if network.mtu
    Chef::Log.info("Using mtu #{network.mtu} for #{network.name} network on #{our_iface.name}")
    ifs[our_iface.name]["mtu"] = network.mtu
  end
  # Make sure our addresses are correct
  if_mapping[network.name] = net_ifs
  ifs[our_iface.name]["addresses"] ||= Array.new
  if addr
    ifs[our_iface.name]["addresses"] << addr
    addr_mapping[network.name] ||= Array.new
    addr_mapping[network.name] << addr.to_s
    # Ditto for our default route
    if network.router_pref && (network.router_pref < route_pref)
      Chef::Log.info("#{network.name}: Will use #{network.router} as our default route")
      route_pref = network.router_pref
      default_route = { nic: our_iface.name, gateway: network.router }
    end
  end
end

Nic.refresh_all

# Kill any nics that we don't want hanging around anymore.
Nic.nics.reverse_each do |nic|
  next if ifs[nic.name]
  # If we are bringing this node under management, kill any nics we did not
  # configure, except for loopback interfaces.
  if old_ifs[nic.name] || !::File.exist?("/var/cache/crowbar/network/managed")
    kill_nic(nic)
  end
end

Nic.refresh_all

# At this point, any new interfaces we need have been configured, we know
# what IP addresses should be assigned to each interface, and we know what
# default route we should use. Make reality match our expectations.
Nic.nics.each do |nic|
  # If this nic is neither in our old config nor in our new config, skip
  next unless ifs[nic.name]
  iface = ifs[nic.name]
  old_iface = old_ifs[nic.name]
  enslaved = false
  # If we are a member of a bond or a bridge, then the bond or bridge
  # gets our config instead of us. The order in which Nic.nics returns
  # interfaces ensures that this will always function properly.
  if (master = nic.master)
    if iface["slave"]
      # We should continue to be a slave.
      Chef::Log.info("#{master.name}: usurping #{nic.name}")
      ifs[nic.name]["addresses"].each{|a|
        ifs[master.name]["addresses"] << a
      }
      ifs[nic.name]["addresses"] = []
      default_route[:nic] = master.name if default_route[:nic] == nic.name
      if_mapping.each { |k,v|
        v << master.name if v.last == nic.name
      }
    elsif !old_ifs[master.name]
      # We have been enslaved to an interface not managed by Crowbar.
      # Skip any further configuration of this nic.
      Chef::Log.info("#{nic.name} is enslaved to #{master.name}, which was not created by Crowbar")
      enslaved = true
    else
      # We no longer want to be a slave.
      Chef::Log.info("#{nic.name} no longer wants to be a slave of #{master.name}")
      master.remove_slave nic
    end
  end

  unless nic.kind_of?(Nic::Vlan) or nic.kind_of?(Nic::Bond)
    nic.rx_offloading = node["network"]["enable_rx_offloading"] || true
    nic.tx_offloading = node["network"]["enable_tx_offloading"] || true
  end

  # Do some MTU checks/configuration for the parent of the vlan nic
  if nic.is_a?(Nic::Vlan)
    if nic.parent
      parent_nic = Nic.new(nic.parent)
      Chef::Log.info("Checking MTU for nic #{nic.name} with parent #{parent_nic.name}")
      if ifs[nic.name].key?("mtu") &&
          ifs.key?(parent_nic.name) &&
          ifs[parent_nic.name].key?("mtu") &&
          ifs[parent_nic.name]["mtu"].to_i < ifs[nic.name]["mtu"].to_i
        # parent has custom mtu lower than the nic custom mtu
        msg = "Interface #{parent_nic.name} has a custom mtu of #{ifs[parent_nic.name]["mtu"]} "\
               "but children vlan #{nic.name} wants a mtu of #{ifs[nic.name]["mtu"]}."\
               "This configuration is invalid."
        Chef::Log.fatal(msg)
        raise msg
      end
      # set the highest mtu of all children in the same interface
      all_children = ifs.select { |k, v| v["parent"] == nic.parent && v.key?("mtu") }

      unless all_children.empty?
        max_mtu_nic = all_children.max_by { |k, v| v["mtu"] }[0]
        ifs[nic.parent]["mtu"] = ifs[max_mtu_nic]["mtu"].to_i
        parent_nic.mtu = ifs[max_mtu_nic]["mtu"].to_i
      end
    end
  end

  if ifs[nic.name]["mtu"]
    nic.mtu = ifs[nic.name]["mtu"]
  end

  if !enslaved
    nic.up
    Chef::Log.info("#{nic.name}: current addresses: #{nic.addresses.map{ |a|a.to_s }.sort.inspect}") unless nic.addresses.empty?
    Chef::Log.info("#{nic.name}: required addresses: #{iface["addresses"].map{ |a|a.to_s }.sort.inspect}") unless iface["addresses"].empty?
    # Ditch old addresses, add new ones.
    old_iface["addresses"].reject{ |i|iface["addresses"].member?(i) }.each do |addr|
      Chef::Log.info("#{nic.name}: Removing #{addr.to_s}")
      nic.remove_address addr
    end if old_iface
    iface["addresses"].reject{ |i|nic.addresses.member?(i) }.each do |addr|
      Chef::Log.info("#{nic.name}: Adding #{addr.to_s}")
      nic.add_address addr
    end
  end

  # Make sure we are using the proper default route.
  if ::Kernel.system("ip route show dev #{nic.name} |grep -q default") &&
      (default_route[:nic] != nic.name)
    Chef::Log.info("Removing default route from #{nic.name}")
    ::Kernel.system("ip route del default dev #{nic.name}")
  elsif default_route[:nic] == nic.name
    ifs[nic.name]["gateway"] = default_route[:gateway]
    unless ::Kernel.system("ip route show dev #{nic.name} |grep -q default")
      Chef::Log.info("Adding default route via #{default_route[:gateway]} to #{nic.name}")
      ::Kernel.system("ip route add default via #{default_route[:gateway]} dev #{nic.name}")
    end
  end
end

if ["delete","reset"].member?(node["state"])
  # We just had the rug pulled out from under us.
  # Do our darndest to get an IP address we can use.
  Chef::Log.info("Node state is #{node["state"]}; ensuring network up")
  Nic.refresh_all
  Nic.nics.each{|n|
    next if n.name =~ /^lo/
    n.up
    break if ::Kernel.system("dhclient -1 #{n.name}")
  }
end

# Wait for the administrative network to come back up.
if provisioner_address
  Chef::Log.info("Checking we can ping #{provisioner_address}; " \
                 "will wait up to 60 seconds")
  require "ipaddr"
  ping_cmd = if IPAddr.new(provisioner_address).ipv6?
    "ping6"
  else
    "ping"
  end

  60.times do
    break if ::Kernel.system("#{ping_cmd} -c 1 -w 1 -q #{provisioner_address} > /dev/null")
    sleep 1
  end
end

node.set["crowbar_wall"] ||= Mash.new
node.set["crowbar_wall"]["network"] ||= Mash.new
saved_ifs = Mash.new
ifs.each {|k,v|
  addrs = v["addresses"].map{ |a|a.to_s }.sort
  saved_ifs[k] = v.dup
  saved_ifs[k]["addresses"] = addrs
}
Chef::Log.info("Saving interfaces to crowbar_wall: #{saved_ifs.inspect}")

if node["crowbar_wall"]["network"]["interfaces"] != saved_ifs
  node.set["crowbar_wall"]["network"]["interfaces"] = saved_ifs
  dirty = true
end
if node["crowbar_wall"]["network"]["nets"] != if_mapping
  node.set["crowbar_wall"]["network"]["nets"] = if_mapping
  dirty = true
end
if node["crowbar_wall"]["network"]["addrs"] != addr_mapping
  node.set["crowbar_wall"]["network"]["addrs"] = addr_mapping
  dirty = true
end

# Flag to let us know that networking on this node
# is now managed by the netowrk barclamp.
FileUtils.mkdir_p("/var/cache/crowbar/network")
FileUtils.touch("/var/cache/crowbar/network/managed")

case node[:platform_family]
when "debian"
  template "/etc/network/interfaces" do
    source "interfaces.erb"
    owner "root"
    group "root"
    variables({ interfaces: ifs })
  end
when "rhel"
  # add redhat-specific code here
  Nic.nics.each do |nic|
    next unless ifs[nic.name]
    template "/etc/sysconfig/network-scripts/ifcfg-#{nic.name}" do
      source "redhat-cfg.erb"
      owner "root"
      group "root"
      variables({
                  interfaces: ifs, # the array of config values
                  nic: nic # the live object representing the current nic.
                })
    end
  end
when "suse"

  ethtool_options = []
  ethtool_options << "rx off" unless node["network"]["enable_rx_offloading"] || true
  ethtool_options << "tx off" unless node["network"]["enable_tx_offloading"] || true
  ethtool_options = ethtool_options.join(" ")

  Nic.nics.each do |nic|
    next unless ifs[nic.name]

    pre_up_script = nil
    if nic.is_a?(Nic::OvsBridge)
      directory "/etc/wicked/scripts/" do
        owner "root"
        group "root"
        mode "0755"
        action :create
      end

      pre_up_script = "/etc/wicked/scripts/#{nic.name}-pre-up"
      ovs_forward_bpdu = ifs[nic.name]["ovs_forward_bpdu"] || false
      is_admin_nwk = if_mapping.key?("admin") && if_mapping["admin"].include?(nic.name)

      nic.ovs_forward_bpdu(ovs_forward_bpdu)

      template pre_up_script do
        owner "root"
        group "root"
        mode "0755"
        source "ovs-pre-up.sh.erb"
        variables(
          bridgename: nic.name,
          ovs_forward_bpdu: ovs_forward_bpdu,
          is_admin_nwk: is_admin_nwk
        )
      end
    end

    template "/etc/sysconfig/network/ifcfg-#{nic.name}" do
      source "suse-cfg.erb"
      variables({
        ethtool_options: ethtool_options,
        interfaces: ifs,
        nic: nic,
        pre_up_script: pre_up_script
      })
      notifies :create, "ruby_block[wicked-ifup-required]", :immediately
    end

    if ifs[nic.name]["gateway"]
      template "/etc/sysconfig/network/ifroute-#{nic.name}" do
        source "suse-route.erb"
        variables({
                    interfaces: ifs,
                    nic: nic
                  })
      end
    else
      file "/etc/sysconfig/network/ifroute-#{nic.name}" do
        action :delete
      end
    end
  end

  run_wicked_ifup = false

  # This, when notified by the above "ifcfg" templates, sets run_wicked_ifup
  # to true (which was initialized to false in the compile phase).
  # run_wicked_ifup is later used as an "only_if" guard for the
  # "wicked ifup all" call that is needs to happen when any of the config
  # files got updated. The purpose of doing it this way (instead of notifying
  # the "wicked-ifup-all" resource directly), is to make sure that the
  # ifup is only run once after all ifcfg file have been updated and
  # independent of how many of them were changed.
  ruby_block "wicked-ifup-required" do
    block do
      run_wicked_ifup = true
    end
    action :nothing
  end

  # Mark all configured interfaces as up, so wicked will keep them that way.
  bash "wicked-ifup-all" do
    action :run
    code "wicked ifup all"
    only_if { run_wicked_ifup }
  end

  # Running 'wicked ifup all' sometimes has an unwanted side effect of removing
  # the default route, so we have to restore it here.
  ruby_block "check-restore-default-route" do
    block do
      gateway = default_route[:gateway]
      unless ::Kernel.system("ip route show dev #{default_route[:nic]} |grep -q default")
        Chef::Log.info("Restoring default route via #{gateway} to #{default_route[:nic]}")
        ::Kernel.system("ip route add default via #{gateway} dev #{default_route[:nic]}")
      end
    end
    only_if { run_wicked_ifup }
  end

  # Avoid running the wicked related thing on SLE11 nodes
  unless node[:platform] == "suse" && node[:platform_version].to_f < 12.0
    if ovs_bridge_created
      # If we're using an ovs-bridge somewhere, enable wicked-nanny to be started
      # for the next boot.
      # Note: There's no need to restart wicked here as all interfaces
      # should be correctly configure at this point.
      template "/etc/wicked/local.conf" do
        source "wicked-local.conf.erb"
        owner "root"
        group "root"
        mode "0644"
        variables(
          use_nanny: true
        )
      end
    else
      # Delete file when we don't need it anymore (to switch back to wicked's
      # default
      file "/etc/wicked/local.conf" do
        action :delete
      end
    end
  end
end

node.save if dirty