root/lib/proxy/dhcp/server/isc.rb @ d1c1f81d
eb305390 | Ohad Levy | require 'time'
|
|
module Proxy::DHCP
|
|||
class ISC < Server
|
|||
7e3ca5c2 | Michael Moll | include Proxy::Util
|
|
eb305390 | Ohad Levy | ||
46d028cd | Ohad Levy | def initialize options
|
|
super(options[:name])
|
|||
fcf097c2 | Mikael Fridh | @config = read_config(options[:config]).join("")
|
|
46d028cd | Ohad Levy | @leases = options[:leases]
|
|
eb305390 | Ohad Levy | end
|
|
def delRecord subnet, record
|
|||
validate_subnet subnet
|
|||
validate_record record
|
|||
raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable?
|
|||
46d028cd | Ohad Levy | msg = "Removed DHCP reservation for #{record.name} => #{record}"
|
|
eb305390 | Ohad Levy | omcmd "connect"
|
|
omcmd "set hardware-address = #{record.mac}"
|
|||
omcmd "open"
|
|||
omcmd "remove"
|
|||
46d028cd | Ohad Levy | omcmd("disconnect", msg)
|
|
subnet.delete record
|
|||
eb305390 | Ohad Levy | end
|
|
def addRecord options = {}
|
|||
f2248acc | Ohad Levy | record = super(options)
|
|
eb305390 | Ohad Levy | ||
omcmd "connect"
|
|||
f2248acc | Ohad Levy | omcmd "set name = \"#{record.name}\""
|
|
omcmd "set ip-address = #{record.ip}"
|
|||
omcmd "set hardware-address = #{record.mac}"
|
|||
eb305390 | Ohad Levy | omcmd "set hardware-type = 1" # This is ethernet
|
|
f2248acc | Ohad Levy | options = record.options
|
|
eb305390 | Ohad Levy | # TODO: Extract this block into a generic dhcp options helper
|
|
statements = []
|
|||
f2248acc | Ohad Levy | statements << "filename = \\\"#{options[:filename]}\\\";" if options[:filename]
|
|
statements << bootServer(options[:nextServer]) if options[:nextServer]
|
|||
statements << "option host-name = \\\"#{record.name}\\\";" if record.name
|
|||
eb305390 | Ohad Levy | ||
0092c5de | dima | statements += solaris_options_statements(options)
|
|
d1c1f81d | Frank Wall | statements += ztp_options_statements(options)
|
|
0092c5de | dima | ||
3d11b301 | Ohad Levy | omcmd "set statements = \"#{statements.join(" ")}\"" unless statements.empty?
|
|
eb305390 | Ohad Levy | omcmd "create"
|
|
f2248acc | Ohad Levy | omcmd("disconnect", "Added DHCP reservation for #{record}")
|
|
record
|
|||
eb305390 | Ohad Levy | end
|
|
def loadSubnetData subnet
|
|||
46d028cd | Ohad Levy | super
|
|
eb305390 | Ohad Levy | conf = format((@config+@leases).split("\n"))
|
|
# scan for host statements
|
|||
conf.scan(/host\s+(\S+\s*\{[^}]+\})/) do |host|
|
|||
46d028cd | Ohad Levy | if match = host[0].match(/^(\S+)\s*\{([^\}]+)/)
|
|
f0e9b8bf | Ohad Levy | hostname = match[1]
|
|
46d028cd | Ohad Levy | body = match[2]
|
|
f0e9b8bf | Ohad Levy | opts = {:hostname => hostname}
|
|
eb305390 | Ohad Levy | body.split(";").each do |data|
|
|
opts.merge!(parse_record_options(data))
|
|||
end
|
|||
if opts[:deleted]
|
|||
f0e9b8bf | Ohad Levy | subnet.delete find_record_by_hostname(subnet, hostname)
|
|
eb305390 | Ohad Levy | next
|
|
end
|
|||
end
|
|||
begin
|
|||
f2248acc | Ohad Levy | Proxy::DHCP::Reservation.new(opts.merge({:subnet => subnet})) if subnet.include? opts[:ip]
|
|
eb305390 | Ohad Levy | rescue Exception => e
|
|
f0e9b8bf | Ohad Levy | logger.warn "skipped #{hostname} - #{e}"
|
|
eb305390 | Ohad Levy | end
|
|
end
|
|||
conf.scan(/lease\s+(\S+\s*\{[^}]+\})/) do |lease|
|
|||
46d028cd | Ohad Levy | if match = lease[0].match(/^(\S+)\s*\{([^\}]+)/)
|
|
9337d9f1 | Ohad Levy | next unless ip = match[1]
|
|
46d028cd | Ohad Levy | body = match[2]
|
|
eb305390 | Ohad Levy | opts = {}
|
|
body.split(";").each do |data|
|
|||
opts.merge! parse_record_options(data)
|
|||
end
|
|||
9337d9f1 | Ohad Levy | next if opts[:state] == "free" or opts[:state] == "abandoned" or opts[:mac].nil?
|
|
Proxy::DHCP::Lease.new(opts.merge({:subnet => subnet, :ip => ip})) if subnet.include? ip
|
|||
eb305390 | Ohad Levy | end
|
|
end
|
|||
46d028cd | Ohad Levy | report "Enumerated hosts on #{subnet.network}"
|
|
eb305390 | Ohad Levy | end
|
|
private
|
|||
def loadSubnets
|
|||
4156b933 | Ohad Levy | super
|
|
eb305390 | Ohad Levy | @config.each_line do |line|
|
|
if line =~ /^\s*subnet\s+([\d\.]+)\s+netmask\s+([\d\.]+)/
|
|||
Proxy::DHCP::Subnet.new(self, $1, $2)
|
|||
end
|
|||
end
|
|||
46d028cd | Ohad Levy | "Enumerated the scopes on #{@name}"
|
|
eb305390 | Ohad Levy | end
|
|
#prepare text for parsing
|
|||
def format text
|
|||
text.delete_if {|line| line.strip.index("#") == 0}
|
|||
return text.map{|l| l.strip.chomp}.join("")
|
|||
end
|
|||
def parse_record_options text
|
|||
options = {}
|
|||
case text
|
|||
# standard record values
|
|||
when /^hardware\s+ethernet\s+(\S+)/
|
|||
options[:mac] = $1
|
|||
when /^fixed-address\s+(\S+)/
|
|||
options[:ip] = $1
|
|||
when /^next-server\s+(\S+)/
|
|||
options[:nextServer] = $1
|
|||
when /^filename\s+(\S+)/
|
|||
options[:filename] = $1
|
|||
# Lease options
|
|||
when /^binding\s+state\s+(\S+)/
|
|||
options[:state] = $1
|
|||
when /^starts\s+\d+\s+(.*)/
|
|||
options[:starts] = parse_time($1)
|
|||
when /^ends\s+\d+\s+(.*)/
|
|||
options[:ends] = parse_time($1)
|
|||
# used for failover - not implemented
|
|||
when /^tstp\s+\d+\s+(.*)/
|
|||
options[:tstp] = parse_time($1)
|
|||
# OMAPI settings
|
|||
when /^deleted/
|
|||
options[:deleted] = true
|
|||
when /^supersede server.next-server\s+=\s+(\S+)/
|
|||
12619dc7 | Ohad Levy | begin
|
|
ns = validate_ip hex2ip($1)
|
|||
rescue
|
|||
ns = $1.gsub("\"","")
|
|||
end
|
|||
options[:nextServer] = ns
|
|||
eb305390 | Ohad Levy | when /^supersede server.filename\s+=\s+"(\S+)"/
|
|
options[:filename] = $1
|
|||
when "dynamic"
|
|||
f2248acc | Ohad Levy | options[:deleteable] = true
|
|
eb305390 | Ohad Levy | #TODO: check if adding a new reservation with omshell for a free lease still
|
|
#generates a conflict
|
|||
end
|
|||
0092c5de | dima | options.merge!(solaris_options_parser(text))
|
|
options
|
|||
eb305390 | Ohad Levy | end
|
|
46d028cd | Ohad Levy | def omcmd cmd, msg=nil
|
|
eb305390 | Ohad Levy | if cmd == "connect"
|
|
7e3ca5c2 | Michael Moll | om_binary = which("omshell")
|
|
@om = IO.popen("/bin/sh -c '#{om_binary} 2>&1'", "r+")
|
|||
025b5ad0 | Ohad Levy | @om.puts "key #{SETTINGS.dhcp_key_name} \"#{SETTINGS.dhcp_key_secret}\"" if SETTINGS.dhcp_key_name and SETTINGS.dhcp_key_secret
|
|
eb305390 | Ohad Levy | @om.puts "server #{name}"
|
|
@om.puts "connect"
|
|||
@om.puts "new host"
|
|||
e0d86f93 | Paul Kelly | elsif cmd == "disconnect"
|
|
eb305390 | Ohad Levy | @om.close_write
|
|
status = @om.readlines
|
|||
@om.close
|
|||
46d028cd | Ohad Levy | @om = nil # we cannot serialize an IO object, even if closed.
|
|
563fb013 | Paul Kelly | report msg, status
|
|
eb305390 | Ohad Levy | else
|
|
a1226adb | Ohad Levy | logger.debug filter_log "omshell: executed - #{cmd}"
|
|
eb305390 | Ohad Levy | @om.puts cmd
|
|
end
|
|||
46d028cd | Ohad Levy | end
|
|
def report msg, response=""
|
|||
ad8bb0c7 | Greg Sutcliffe | if response.nil? or (!response.empty? and !response.grep(/can't|no more|not connected|Syntax error/).empty?)
|
|
563fb013 | Paul Kelly | logger.error "Omshell failed:\n" + (response.nil? ? "No response from DHCP server" : response.join(", "))
|
|
46d028cd | Ohad Levy | msg.sub! /Removed/, "remove"
|
|
msg.sub! /Added/, "add"
|
|||
msg.sub! /Enumerated/, "enumerate"
|
|||
msg = "Failed to #{msg}"
|
|||
563fb013 | Paul Kelly | msg += ": Entry already exists" if response and response.grep(/object: already exists/).size > 0
|
|
msg += ": No response from DHCP server" if response.nil? or response.grep(/not connected/).size > 0
|
|||
8dc95e7c | Mikael Fridh | raise Proxy::DHCP::Collision, "Hardware address conflict." if response and response.grep(/object: key conflict/).size > 0
|
|
46d028cd | Ohad Levy | raise Proxy::DHCP::Error.new(msg)
|
|
eb305390 | Ohad Levy | else
|
|
46d028cd | Ohad Levy | logger.info msg
|
|
563fb013 | Paul Kelly | end
|
|
eb305390 | Ohad Levy | end
|
|
def ip2hex ip
|
|||
ip.split(".").map{|i| "%02x" % i }.join(":")
|
|||
end
|
|||
def hex2ip hex
|
|||
hex.split(":").map{|h| h.to_i(16).to_s}.join(".")
|
|||
end
|
|||
f0e9b8bf | Ohad Levy | def find_record_by_hostname subnet, hostname
|
|
eb305390 | Ohad Levy | subnet.records.each do |v|
|
|
f0e9b8bf | Ohad Levy | return v if v.options[:hostname] == hostname
|
|
eb305390 | Ohad Levy | end
|
|
end
|
|||
# ISC stores timestamps in UTC, therefor forcing the time to load from GMT/UTC TZ
|
|||
def parse_time str
|
|||
Time.parse(str +" UTC")
|
|||
rescue => e
|
|||
logger.warn "Unable to parse time #{e}"
|
|||
raise "Unable to parse time #{e}"
|
|||
end
|
|||
e5cfb588 | Ohad Levy | def bootServer server
|
|
begin
|
|||
057a6378 | Ohad Levy | ns = ip2hex validate_ip(server)
|
|
e5cfb588 | Ohad Levy | rescue
|
|
begin
|
|||
057a6378 | Ohad Levy | ns = ip2hex Resolv.new.getaddress(server)
|
|
e5cfb588 | Ohad Levy | rescue
|
|
logger.warn "Failed to resolve IP address for #{server}"
|
|||
ns = "\\\"#{server}\\\""
|
|||
end
|
|||
end
|
|||
"next-server = #{ns};"
|
|||
end
|
|||
a1226adb | Ohad Levy | def filter_log log
|
|
secret = SETTINGS.dhcp_key_secret
|
|||
if secret.is_a?(String) and not secret.empty?
|
|||
log.gsub!(SETTINGS.dhcp_key_secret,"[filtered]")
|
|||
end
|
|||
logger.debug log
|
|||
end
|
|||
fcf097c2 | Mikael Fridh | def read_config file
|
|
logger.debug "Reading config file #{file}"
|
|||
config = []
|
|||
File.readlines(file).each do |line|
|
|||
if /^include\s+"(.*)"\s*;/ =~ line.strip
|
|||
conf = $1
|
|||
unless File.exist?(conf)
|
|||
f3923961 | Ohad Levy | raise "Unable to find the included DHCP configuration file: #{conf}"
|
|
fcf097c2 | Mikael Fridh | end
|
|
config << read_config(conf)
|
|||
else
|
|||
config << line
|
|||
end
|
|||
end
|
|||
return config
|
|||
end
|
|||
0092c5de | dima | def vendor_options_supported?
|
|
true
|
|||
end
|
|||
def solaris_options_statements(options)
|
|||
# Solaris options defined in Foreman app/models/operatingsystems/solaris.rb method jumpstart_params
|
|||
# options example
|
|||
# {"hostname" => ["itgsyddev910.macbank"],
|
|||
# "mac" => ["00:21:28:6d:62:e8"],
|
|||
# "ip" => ["10.229.11.38"],
|
|||
# "network" => ["10.229.11.0"],
|
|||
# "nextServer" => ["10.229.11.24"], "filename" => ["Solaris-5.10-hw0811-sun4v-inetboot"],
|
|||
# "<SPARC-Enterprise-T5120>root_path_name" => ["/Solaris/install/Solaris_5.10_sparc_hw0811/Solaris_10/Tools/Boot"],
|
|||
# "<SPARC-Enterprise-T5120>sysid_server_path" => ["10.229.11.24:/Solaris/jumpstart/sysidcfg/sysidcfg_primary"],
|
|||
# "<SPARC-Enterprise-T5120>install_server_ip" => ["10.229.11.24"],
|
|||
# "<SPARC-Enterprise-T5120>jumpstart_server_path" => ["10.229.11.24:/Solaris/jumpstart"],
|
|||
# "<SPARC-Enterprise-T5120>install_server_name" => ["itgsyddev807.macbank"],
|
|||
# "<SPARC-Enterprise-T5120>root_server_hostname" => ["itgsyddev807.macbank"],
|
|||
# "<SPARC-Enterprise-T5120>root_server_ip" => ["10.229.11.24"],
|
|||
# "<SPARC-Enterprise-T5120>install_path" => ["/Solaris/install/Solaris_5.10_sparc_hw0811"] }
|
|||
#
|
|||
statements = []
|
|||
options.each do |key, value|
|
|||
next unless (match = key.to_s.match(/^<([^>]+)>(.*)/))
|
|||
vendor, attr = match[1, 2].map(&:to_sym)
|
|||
next unless vendor.to_s =~ /sun|solar|sparc/i
|
|||
case attr
|
|||
when :jumpstart_server_path
|
|||
statements << "option SUNW.JumpStart-server \\\"#{value}\\\";"
|
|||
when :sysid_server_path
|
|||
statements << "option SUNW.sysid-config-file-server \\\"#{value}\\\";"
|
|||
when :install_server_name
|
|||
statements << "option SUNW.install-server-hostname \\\"#{value}\\\";"
|
|||
when :install_server_ip
|
|||
statements << "option SUNW.install-server-ip-address #{value};"
|
|||
when :install_path
|
|||
statements << "option SUNW.install-path \\\"#{value}\\\";"
|
|||
when :root_server_hostname
|
|||
statements << "option SUNW.root-server-hostname \\\"#{value}\\\";"
|
|||
when :root_server_ip
|
|||
statements << "option SUNW.root-server-ip-address #{value};"
|
|||
when :root_path_name
|
|||
statements << "option SUNW.root-path-name \\\"#{value}\\\";"
|
|||
end
|
|||
end
|
|||
statements << 'vendor-option-space SUNW;' if statements.join(' ') =~ /SUNW/
|
|||
statements
|
|||
end
|
|||
def solaris_options_parser(text)
|
|||
options = {}
|
|||
case text
|
|||
when 'vendor-option-space SUNW'
|
|||
options[:vendor] = 'sun'
|
|||
when /^option SUNW.root-server-ip-address\s+(\S+)/
|
|||
options[:root_server_ip] = $1
|
|||
when /^option SUNW.root-server-hostname\s+(\S+)/
|
|||
options[:root_server_hostname] = $1
|
|||
when /^option SUNW.root-path-name\s+(\S+)/
|
|||
options[:root_path_name] = $1
|
|||
when /^option SUNW.install-server-ip-address\s+(\S+)/
|
|||
options[:install_server_ip] = $1
|
|||
when /^option SUNW.install-server-hostname\s+(\S+)/
|
|||
options[:install_server_name] = $1
|
|||
when /^option SUNW.install-path\s+(\S+)/
|
|||
options[:install_path] = $1
|
|||
when /^option SUNW.sysid-config-file-server\s+(\S+)/
|
|||
options[:sysid_server_path] = $1
|
|||
when /^option SUNW.JumpStart-server\s+(\S+)/
|
|||
options[:jumpstart_server_path] = $1
|
|||
end
|
|||
options
|
|||
end
|
|||
d1c1f81d | Frank Wall | ||
# Quirk: Junos ZTP requires special DHCP options
|
|||
def ztp_options_statements(options)
|
|||
statements = []
|
|||
if options[:filename] && options[:filename].match(/^ztp.cfg.*/i)
|
|||
logger.debug "setting ZTP options"
|
|||
opt150 = ip2hex validate_ip(options[:nextServer])
|
|||
statements << "option option-150 = #{opt150};"
|
|||
statements << "option FM_ZTP.config-file-name = \\\"#{options[:filename]}\\\";"
|
|||
end
|
|||
statements
|
|||
end
|
|||
eb305390 | Ohad Levy | end
|
|
end
|