cookbooks/fb_network_scripts/resources/default.rb (151 lines of code) (raw):
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
#
# Copyright (c) 2012-present, Facebook, Inc.
# All rights reserved.
#
# 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.
#
default_action :create
action_class do
def build_config
configs = {}
bridging = false
# If someone wants their primary interface to be something other
# than eth0, then we need to make sure we process that interface...
primary_int = node['fb_network_scripts']['primary_interface']
unless node['fb_network_scripts']['interfaces'][primary_int]
node.default['fb_network_scripts']['interfaces'][primary_int] = {}
end
# and also, lets clean up our default, assuming they haven't touched it
if primary_int != 'eth0' &&
node['fb_network_scripts']['interfaces']['eth0'] &&
node['fb_network_scripts']['interfaces']['eth0'].empty?
node.rm(:fb_network_scripts, :interfaces, :eth0)
end
interface_configs = node['fb_network_scripts']['interfaces'].to_hash
interface_configs.each do |interface, config|
# Some defaults
config['name'] = interface
unless config['onboot']
config['onboot'] = 'yes'
end
unless config['bootproto']
config['bootproto'] = 'static'
end
unless config['v6router']
config['v6router'] = 'no'
end
unless config['ipv6_autoconf']
config['ipv6_autoconf'] = 'yes'
end
# Some sanity checking
if [false, 'no', 'n', 'N', 'NO'].include?(config['onboot'])
config['onboot'] = 'no'
else
config['onboot'] = 'yes'
end
if [true, 'yes', 'y', 'Y', 'YES'].include?(config['v6router'])
config['v6router'] = 'yes'
else
config['v6router'] = 'no'
end
Chef::Log.debug("NETCONF: #{interface} #{config}")
have_v4_config = (config.key?('ip') && config.key?('netmask')) ||
config.key?('my_inner_ipaddr')
have_v6_config = config.key?('ipv6')
want_auto = (config['bootproto'] == 'static' &&
!have_v4_config && !have_v6_config)
if config['range']
# In the IPv4 world, if we use ranges on tunl0, we set the tunl0
# interface to 127.0.0.2.
config['ip'] = '127.0.0.2'
config['netmask'] = '255.255.255.0'
elsif want_auto
if interface == primary_int
unless config['ip'] || config['ipv6']
fail "fb_network_scripts: #{interface} has neither IPv4 nor " +
'IPv6 address, aborting'
end
# If we are a bridge *member* we should not have a boot proto
elsif config['bridge'] || config['ovs_bridge']
config.delete('bootproto')
bridging = true
else
# But we don't do that on non-primary interfaces, because otherwise
Chef::Log.error(
"fb_network_scripts: you requested that #{interface} be " +
"configured, but didn't provide configuration data, (and _also_" +
"it's not the primary interface [#{primary_int}], so it cannot " +
'be configured!',
)
fail 'fb_network_scripts: not enough data to configure ' +
"#{interface}, cowardly refusing to continue."
end
end
# This is not part of the else-if above because one could have a
# v6 range *and* other stuff because v6ranges aren't subinterfaces
# they're just additional addresses.
if config['v6range']
# Unlock range files, IPv6 config files can't do ranges, so we
# take that range syntax and expand it into the full list of IPs
# and list them out in IPV6SECONDARIES
unless config['v6secondaries']
config['v6secondaries'] = []
end
config['v6secondaries'] += FB::NetworkScripts.v6range2list(
config['v6range']['start'], config['v6range']['end']
)
unless config['ipv6']
config['ipv6'] = config['v6secondaries'].shift
end
end
configs[interface] = config
end
configs.values
end
end
# We create a bunch of resources here which people expect to be able to notify,
# so we cannot use notifying_action which will create a subcontext.
#
# Note that for redhat, we will automatically build all the right services
# and notifications, and so we never report that we changed things
action :create do
configs = build_config
nodelete = ['lo', node['fb_network_scripts']['primary_interface']]
primary_int = node['fb_network_scripts']['primary_interface']
is_v6_enabled = ::File.exist?('/proc/sys/net/ipv6')
ifup_sysctl = {}
configs.each do |config|
iface = config['name']
nodelete << iface
is_new_iface = !::File.exist?("/sys/class/net/#{iface}")
# When we create a bridge for the first time (or when we setup a bridge
# member interface), setup the config but do not start it. We do this
# because there's no concept of dependencies between interfaces, and
# starting a bridge but not his members (or viceversa) will kill the
# network, which will cause the Chef run to fail. Because the interfaces
# have been modified, both the bridge and its members will be (re)started
# together at the end of the Chef run, thus ensuring we don't lose
# connectivity.
if (iface.start_with?('br', 'ovsbr') &&
iface == primary_int && is_new_iface) ||
(config['bridge'] || config['ovs_bridge'])
iface_action = [:enable]
else
iface_action = [:enable, :start]
end
# You'd think we could use net.ipv6.conf.default.* for this, but it
# doesn't actually work in the bridge scenario, for two reasons:
# - defaults are applied when the interface node is created, so they won't
# affect bridge members (which already exist)
# - for some reason defaults are completely ignored for bridge interfaces
# Additionally, for every interface with IPv6 enabled the ifup-ipv6
# script will helpfully reset these sysctl to whatever it thinks is right
# -- which is generally not what we want -- on every interface (re)start
if is_v6_enabled && iface == primary_int
ifup_sysctl = {
"net.ipv6.conf.#{iface}.autoconf" => 0,
"net.ipv6.conf.#{iface}.accept_ra" => 1,
"net.ipv6.conf.#{iface}.accept_ra_pinfo" => 0,
}
elsif is_v6_enabled && (config['bridge'] || config['ovs_bridge'])
ifup_sysctl = {
"net.ipv6.conf.#{iface}.disable_ipv6" => 0,
}
end
ifup_sysctl.each do |key, val|
# These are then consumed by ifup-local.erb which gets executed
# after interface (re)start. This template is defined in the recipe
# fb_network_scripts::default and will be processed after this provider.
node.default['fb_network_scripts']['ifup']['sysctl'][key] = val
end
fb_network_scripts_redhat_interface iface do
config config
action iface_action
# Restart only if interface change is allowed
if node.interface_change_allowed?(iface)
notifies :restart, "fb_network_scripts_redhat_interface[#{iface}]"
end
end
end
#
# Delete dangling interfaces
#
nodelete.uniq!
# Walk for interfaces up that we don't know about
Dir.glob('/sys/class/net/*').each do |path|
iface = ::File.basename(path)
unless nodelete.include?(iface)
operfile = "/sys/class/net/#{iface}/operstate"
unless ::File.read(operfile).strip == 'down'
# If it's an eth* interface and we don't know about it, shut it down.
#
# In theory we'd do this for all interfaces, but things like proxygen
# may manage their own virtual interfaces (tunl0, iptnl0, other), so
# for those we just warn, to be safe.
if iface.start_with?('eth')
fb_network_scripts_redhat_interface iface do
action [:stop, :disable]
end
else
Chef::Log.warn(
"fb_network_scripts: interface #{iface} running but not Chef " +
'controlled',
)
end
end
end
end
# Next, anything configured we don't know about
Dir.glob('/etc/sysconfig/network-scripts/ifcfg-*').each do |path|
fname = ::File.basename(path)
iface = fname.split('-')[1..-1].join('-').sub('-range', '')
next if nodelete.include?(iface)
fb_network_scripts_redhat_interface iface do
action [:stop, :disable]
end
end
end