Project

General

Profile

Download (8.29 KB) Statistics
| Branch: | Tag: | Revision:
require 'time'
require 'dhcp_common/server'

module Proxy::DHCP::CommonISC
class IscOmapiProvider < ::Proxy::DHCP::Server
include Proxy::Util
attr_reader :omapi_port, :key_name, :key_secret

def initialize(server, omapi_port, subnets = nil, key_name = nil, key_secret = nil, service = nil, free_ips_service = nil)
super(server, subnets, service, free_ips_service)
# TODO: verify key name and secret
@key_name = key_name
@key_secret = key_secret
@omapi_port = omapi_port
end

def del_record(record)
validate_record record
raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable?

om_connect
omcmd "set hardware-address = #{record.mac}"
omcmd "open"
omcmd "remove"
om_disconnect("Removed DHCP reservation for #{record.name} => #{record}")
end

def add_record(options = {})
record = super(options)
om_add_record(record)
record
end

def om_add_record(record)
om_connect
omcmd "set name = \"#{record.name}\""
omcmd "set ip-address = #{record.ip}"
omcmd "set hardware-address = #{record.mac}"
omcmd "set hardware-type = 1" # This is ethernet

options = record.options
# TODO: Extract this block into a generic dhcp options helper
statements = []
statements << "filename = \\\"#{options[:filename]}\\\";" if options[:filename]
statements << "next-server = #{bootServer(options[:nextServer])};" if options[:nextServer]
statements << "option host-name = \\\"#{options[:hostname] || record.name}\\\";"

statements += solaris_options_statements(options)
statements += ztp_options_statements(options)
statements += poap_options_statements(options)

omcmd "set statements = \"#{statements.join(' ')}\"" unless statements.empty?
omcmd "create"
om_disconnect("Added DHCP reservation for #{record}")
end

def om
return @om unless @om.nil?
om_binary = which("omshell")
@om = IO.popen("/bin/sh -c '#{om_binary} 2>&1'", "r+")
end

def om_connect
omcmd("key #{@key_name} \"#{@key_secret}\"", true) if @key_name && @key_secret
omcmd "server #{name}"
omcmd "port #{@omapi_port}"
omcmd "connect"
omcmd "new host"
end

def omcmd(command, filter_key = false)
om.puts(command)
command = command.gsub(/key .*/, "key [filtered] [filtered]") if filter_key
logger.debug "omshell> #{command}"
end

def om_disconnect(msg)
om.close_write
status = om.readlines
om.close
report msg, status
nil
ensure
@om = nil # we cannot serialize an IO object, even if closed.
end

def format_omshell_output(output)
output.map {|x| "omshell= #{x.chomp}"}.join("\n")
end

def report(msg, response="")
logger.debug(format_omshell_output(response))
if response.nil? || (!response.empty? && !response.grep(/can't|no more|not connected|Syntax error/).empty?)
logger.error "Omshell failed: " + (response.nil? ? "Problem launching omshell" : format_omshell_output(response))
msg.sub!(/Removed/, "remove")
msg.sub!(/Added/, "add")
msg.sub!(/Enumerated/, "enumerate")
msg = "Failed to #{msg}"
msg += ": Entry already exists" if response && !response.grep(/object: already exists/).empty?
msg += ": No response from DHCP server" if response.nil? || !response.grep(/(not connected|no more)/).empty?
raise Proxy::DHCP::Collision, "Hardware address conflict." if response && !response.grep(/object: key conflict/).empty?
raise Proxy::DHCP::InvalidRecord if response && !response.grep(/can\'t open object: not found/).empty?
raise Proxy::DHCP::Error.new(msg)
else
logger.debug msg
end
end

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 vendor_specific_ztp_statements(options)
statements = []
if options.has_key?(:ztp_vendor)
case options[:ztp_vendor]
when "huawei"
if options.has_key?(:ztp_firmware)
statements << "option option-143 = \\\"vrpfile=#{options[:ztp_firmware][:core]};webfile=#{options[:ztp_firmware][:web]};\\\";"
end
end
end
statements
end

# Quirk: Junos ZTP requires special DHCP options
def ztp_options_statements(options)
statements = []
if options[:filename]&.match(/^ztp.cfg.*/i)
logger.debug "setting ZTP options"
statements << "option option-150 = #{bootServer(options[:nextServer])};" if options[:nextServer]
statements << "option FM_ZTP.config-file-name = \\\"#{options[:filename]}\\\";"

statements.concat(vendor_specific_ztp_statements(options))
end
statements
end

# Cisco NX-OS POAP requires special DHCP options
def poap_options_statements(options)
statements = []
if options[:filename]&.match(/^poap.cfg.*/i)
logger.debug "setting POAP options"
statements << "option tftp-server-name = \\\"#{options[:nextServer]}\\\";"
statements << "option bootfile-name = \\\"#{options[:filename]}\\\";"
end
statements
end

def bootServer(server)
ip2hex(validate_ip(server))
rescue
begin
logger.info "Next-server option not IPv4, trying to resolve '#{server}'"
ip2hex(dns_resolv.getaddress(server))
rescue Resolv::ResolvError => e
logger.warn "Unable to resolve PTR query for '#{server}', will use the hostname"
logger.debug "Reason: #{e}"
# use hostname as the next-server entry
"\\\"#{server}\\\""
end
end

def ip2hex(ip)
ip.to_s.split(".").map {|i| "%02x" % i }.join(":")
end
end
end
(2-2/3)