File Manager
Viewing File: route.rb
#
# Author:: Bryan McLellan (btm@loftninjas.org), Jesse Nelson (spheromak@gmail.com)
# Copyright:: Copyright 2009-2016, Bryan McLellan
# License:: Apache License, Version 2.0
#
# 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.
#
require "chef/log"
require "chef/mixin/command"
require "chef/provider"
require "ipaddr"
class Chef
class Provider
class Route < Chef::Provider
include Chef::Mixin::Command
provides :route
attr_accessor :is_running
MASK = { "0.0.0.0" => "0",
"128.0.0.0" => "1",
"192.0.0.0" => "2",
"224.0.0.0" => "3",
"240.0.0.0" => "4",
"248.0.0.0" => "5",
"252.0.0.0" => "6",
"254.0.0.0" => "7",
"255.0.0.0" => "8",
"255.128.0.0" => "9",
"255.192.0.0" => "10",
"255.224.0.0" => "11",
"255.240.0.0" => "12",
"255.248.0.0" => "13",
"255.252.0.0" => "14",
"255.254.0.0" => "15",
"255.255.0.0" => "16",
"255.255.128.0" => "17",
"255.255.192.0" => "18",
"255.255.224.0" => "19",
"255.255.240.0" => "20",
"255.255.248.0" => "21",
"255.255.252.0" => "22",
"255.255.254.0" => "23",
"255.255.255.0" => "24",
"255.255.255.128" => "25",
"255.255.255.192" => "26",
"255.255.255.224" => "27",
"255.255.255.240" => "28",
"255.255.255.248" => "29",
"255.255.255.252" => "30",
"255.255.255.254" => "31",
"255.255.255.255" => "32" }.freeze
def hex2ip(hex_data)
# Cleanup hex data
hex_ip = hex_data.to_s.downcase.gsub(/[^0-9a-f]/, "")
# Check hex data format (IP is a 32bit integer, so should be 8 chars long)
return nil if hex_ip.length != hex_data.length || hex_ip.length != 8
# Extract octets from hex data
octets = hex_ip.scan(/../).reverse.collect { |octet| [octet].pack("H2").unpack("C").first }
# Validate IP
ip = octets.join(".")
begin
IPAddr.new(ip, Socket::AF_INET).to_s
rescue ArgumentError
Chef::Log.debug("Invalid IP address data: hex=#{hex_ip}, ip=#{ip}")
return nil
end
end
def whyrun_supported?
true
end
def load_current_resource
self.is_running = false
# cidr or quad dot mask
new_ip = if new_resource.netmask
IPAddr.new("#{new_resource.target}/#{new_resource.netmask}")
else
IPAddr.new(new_resource.target)
end
# For linux, we use /proc/net/route file to read proc table info
return if node[:os] != "linux"
route_file = ::File.open("/proc/net/route", "r")
# Read all routes
while (line = route_file.gets)
# Get all the fields for a route
_, destination, gateway, _, _, _, _, mask = line.split
# Convert hex-encoded values to quad-dotted notation (e.g. 0064A8C0 => 192.168.100.0)
destination = hex2ip(destination)
gateway = hex2ip(gateway)
mask = hex2ip(mask)
# Skip formatting lines (header, etc)
next unless destination && gateway && mask
Chef::Log.debug("#{new_resource} system has route: dest=#{destination} mask=#{mask} gw=#{gateway}")
# check if what were trying to configure is already there
# use an ipaddr object with ip/mask this way we can have
# a new resource be in cidr format (i don't feel like
# expanding bitmask by hand.
#
running_ip = IPAddr.new("#{destination}/#{mask}")
Chef::Log.debug("#{new_resource} new ip: #{new_ip.inspect} running ip: #{running_ip.inspect}")
self.is_running = true if running_ip == new_ip && gateway == new_resource.gateway
end
route_file.close
end
def action_add
# check to see if load_current_resource found the route
if is_running
Chef::Log.debug("#{new_resource} route already active - nothing to do")
else
command = generate_command(:add)
converge_by("run #{command.join(' ')} to add route") do
shell_out_compact!(command)
Chef::Log.info("#{new_resource} added")
end
end
# for now we always write the file (ugly but its what it is)
generate_config
end
def action_delete
if is_running
command = generate_command(:delete)
converge_by("run #{command.join(' ')} to delete route ") do
shell_out_compact!(command)
Chef::Log.info("#{new_resource} removed")
end
else
Chef::Log.debug("#{new_resource} route does not exist - nothing to do")
end
# for now we always write the file (ugly but its what it is)
generate_config
end
def generate_config
conf = {}
case node[:platform]
when "centos", "redhat", "fedora"
# walk the collection
run_context.resource_collection.each do |resource|
next unless resource.is_a? Chef::Resource::Route
# default to eth0
dev = if resource.device
resource.device
else
"eth0"
end
conf[dev] = "" if conf[dev].nil?
case @action
when :add
conf[dev] << config_file_contents(:add, target: resource.target, netmask: resource.netmask, gateway: resource.gateway) if resource.action == [:add]
when :delete
# need to do this for the case when the last route on an int
# is removed
conf[dev] << config_file_contents(:delete)
end
end
conf.each do |k, v|
network_file_name = "/etc/sysconfig/network-scripts/route-#{k}"
converge_by("write route route.#{k}\n#{conf[k]} to #{network_file_name}") do
network_file = ::File.new(network_file_name, "w")
network_file.puts(conf[k])
Chef::Log.debug("#{new_resource} writing route.#{k}\n#{conf[k]}")
network_file.close
end
end
end
end
def generate_command(action)
target = new_resource.target
target = "#{target}/#{MASK[new_resource.netmask.to_s]}" if new_resource.netmask
case action
when :add
command = [ "ip", "route", "replace", target ]
command += [ "via", new_resource.gateway ] if new_resource.gateway
command += [ "dev", new_resource.device ] if new_resource.device
when :delete
command = [ "ip", "route", "delete", target ]
command += [ "via", new_resource.gateway ] if new_resource.gateway
end
command
end
def config_file_contents(action, options = {})
content = ""
case action
when :add
content << (options[:target]).to_s
content << "/#{MASK[options[:netmask].to_s]}" if options[:netmask]
content << " via #{options[:gateway]}" if options[:gateway]
content << "\n"
end
content
end
end
end
end