PageRenderTime 72ms CodeModel.GetById 46ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/dhcp_common/isc/omapi_provider.rb

https://github.com/theforeman/smart-proxy
Ruby | 221 lines | 201 code | 17 blank | 3 comment | 7 complexity | 30438cd1d7fa86bf4b446ae33909487c MD5 | raw file
  1. require 'time'
  2. require 'dhcp_common/server'
  3. module Proxy::DHCP::CommonISC
  4. class IscOmapiProvider < ::Proxy::DHCP::Server
  5. include Proxy::Util
  6. attr_reader :omapi_port, :key_name, :key_secret
  7. def initialize(server, omapi_port, subnets = nil, key_name = nil, key_secret = nil, service = nil, free_ips_service = nil)
  8. super(server, subnets, service, free_ips_service)
  9. # TODO: verify key name and secret
  10. @key_name = key_name
  11. @key_secret = key_secret
  12. @omapi_port = omapi_port
  13. end
  14. def validate_supported_address(*args)
  15. args.each do |ip|
  16. validate_ip(ip, 4)
  17. end
  18. end
  19. def del_record(record)
  20. validate_record record
  21. raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable?
  22. om_connect
  23. omcmd "set hardware-address = #{record.mac}"
  24. omcmd "open"
  25. omcmd "remove"
  26. om_disconnect("Removed DHCP reservation for #{record.name} => #{record}")
  27. end
  28. def add_record(options = {})
  29. record = super(options)
  30. om_add_record(record)
  31. record
  32. end
  33. def om_add_record(record)
  34. om_connect
  35. omcmd "set name = \"#{record.name}\""
  36. omcmd "set ip-address = #{record.ip}"
  37. omcmd "set hardware-address = #{record.mac}"
  38. omcmd "set hardware-type = 1" # This is ethernet
  39. options = record.options
  40. # TODO: Extract this block into a generic dhcp options helper
  41. statements = []
  42. statements << "filename = \\\"#{options[:filename]}\\\";" if options[:filename]
  43. statements << "next-server = #{bootServer(options[:nextServer])};" if options[:nextServer]
  44. statements << "option host-name = \\\"#{options[:hostname] || record.name}\\\";"
  45. statements += solaris_options_statements(options)
  46. statements += ztp_options_statements(options)
  47. statements += poap_options_statements(options)
  48. omcmd "set statements = \"#{statements.join(' ')}\"" unless statements.empty?
  49. omcmd "create"
  50. om_disconnect("Added DHCP reservation for #{record}")
  51. end
  52. def om
  53. return @om unless @om.nil?
  54. om_binary = which("omshell")
  55. @om = IO.popen("/bin/sh -c '#{om_binary} 2>&1'", "r+")
  56. end
  57. def om_connect
  58. omcmd("key #{@key_name} \"#{@key_secret}\"", true) if @key_name && @key_secret
  59. omcmd "server #{name}"
  60. omcmd "port #{@omapi_port}"
  61. omcmd "connect"
  62. omcmd "new host"
  63. end
  64. def omcmd(command, filter_key = false)
  65. om.puts(command)
  66. command = command.gsub(/key .*/, "key [filtered] [filtered]") if filter_key
  67. logger.debug "omshell> #{command}"
  68. end
  69. def om_disconnect(msg)
  70. om.close_write
  71. status = om.readlines
  72. om.close
  73. report msg, status
  74. nil
  75. ensure
  76. @om = nil # we cannot serialize an IO object, even if closed.
  77. end
  78. def format_omshell_output(output)
  79. output.map { |x| "omshell= #{x.chomp}" }.join("\n")
  80. end
  81. def report(msg, response = "")
  82. logger.debug(format_omshell_output(response))
  83. if response.nil? || (!response.empty? && !response.grep(/can't|no more|not connected|Syntax error/).empty?)
  84. logger.error "Omshell failed: " + (response.nil? ? "Problem launching omshell" : format_omshell_output(response))
  85. msg.sub!(/Removed/, "remove")
  86. msg.sub!(/Added/, "add")
  87. msg.sub!(/Enumerated/, "enumerate")
  88. msg = "Failed to #{msg}"
  89. msg += ": Entry already exists" if response && !response.grep(/object: already exists/).empty?
  90. msg += ": No response from DHCP server" if response.nil? || !response.grep(/(not connected|no more)/).empty?
  91. raise Proxy::DHCP::Collision, "Hardware address conflict." if response && !response.grep(/object: key conflict/).empty?
  92. raise Proxy::DHCP::InvalidRecord if response && !response.grep(/can\'t open object: not found/).empty?
  93. raise Proxy::DHCP::Error.new(msg)
  94. else
  95. logger.debug msg
  96. end
  97. end
  98. def vendor_options_supported?
  99. true
  100. end
  101. def solaris_options_statements(options)
  102. # Solaris options defined in Foreman app/models/operatingsystems/solaris.rb method jumpstart_params
  103. # options example
  104. # {"hostname" => ["itgsyddev910.macbank"],
  105. # "mac" => ["00:21:28:6d:62:e8"],
  106. # "ip" => ["10.229.11.38"],
  107. # "network" => ["10.229.11.0"],
  108. # "nextServer" => ["10.229.11.24"], "filename" => ["Solaris-5.10-hw0811-sun4v-inetboot"],
  109. # "<SPARC-Enterprise-T5120>root_path_name" => ["/Solaris/install/Solaris_5.10_sparc_hw0811/Solaris_10/Tools/Boot"],
  110. # "<SPARC-Enterprise-T5120>sysid_server_path" => ["10.229.11.24:/Solaris/jumpstart/sysidcfg/sysidcfg_primary"],
  111. # "<SPARC-Enterprise-T5120>install_server_ip" => ["10.229.11.24"],
  112. # "<SPARC-Enterprise-T5120>jumpstart_server_path" => ["10.229.11.24:/Solaris/jumpstart"],
  113. # "<SPARC-Enterprise-T5120>install_server_name" => ["itgsyddev807.macbank"],
  114. # "<SPARC-Enterprise-T5120>root_server_hostname" => ["itgsyddev807.macbank"],
  115. # "<SPARC-Enterprise-T5120>root_server_ip" => ["10.229.11.24"],
  116. # "<SPARC-Enterprise-T5120>install_path" => ["/Solaris/install/Solaris_5.10_sparc_hw0811"] }
  117. #
  118. statements = []
  119. options.each do |key, value|
  120. next unless (match = key.to_s.match(/^<([^>]+)>(.*)/))
  121. vendor, attr = match[1, 2].map(&:to_sym)
  122. next unless vendor.to_s =~ /sun|solar|sparc/i
  123. case attr
  124. when :jumpstart_server_path
  125. statements << "option SUNW.JumpStart-server \\\"#{value}\\\";"
  126. when :sysid_server_path
  127. statements << "option SUNW.sysid-config-file-server \\\"#{value}\\\";"
  128. when :install_server_name
  129. statements << "option SUNW.install-server-hostname \\\"#{value}\\\";"
  130. when :install_server_ip
  131. statements << "option SUNW.install-server-ip-address #{value};"
  132. when :install_path
  133. statements << "option SUNW.install-path \\\"#{value}\\\";"
  134. when :root_server_hostname
  135. statements << "option SUNW.root-server-hostname \\\"#{value}\\\";"
  136. when :root_server_ip
  137. statements << "option SUNW.root-server-ip-address #{value};"
  138. when :root_path_name
  139. statements << "option SUNW.root-path-name \\\"#{value}\\\";"
  140. end
  141. end
  142. statements << 'vendor-option-space SUNW;' if statements.join(' ') =~ /SUNW/
  143. statements
  144. end
  145. def vendor_specific_ztp_statements(options)
  146. statements = []
  147. if options.has_key?(:ztp_vendor)
  148. case options[:ztp_vendor]
  149. when "huawei"
  150. if options.has_key?(:ztp_firmware)
  151. statements << "option option-143 = \\\"vrpfile=#{options[:ztp_firmware][:core]};webfile=#{options[:ztp_firmware][:web]};\\\";"
  152. end
  153. end
  154. end
  155. statements
  156. end
  157. # Quirk: Junos ZTP requires special DHCP options
  158. def ztp_options_statements(options)
  159. statements = []
  160. if options[:filename]&.match(/^ztp.cfg.*/i)
  161. logger.debug "setting ZTP options"
  162. statements << "option option-150 = #{bootServer(options[:nextServer])};" if options[:nextServer]
  163. statements << "option FM_ZTP.config-file-name = \\\"#{options[:filename]}\\\";"
  164. statements.concat(vendor_specific_ztp_statements(options))
  165. end
  166. statements
  167. end
  168. # Cisco NX-OS POAP requires special DHCP options
  169. def poap_options_statements(options)
  170. statements = []
  171. if options[:filename]&.match(/^poap.cfg.*/i)
  172. logger.debug "setting POAP options"
  173. statements << "option tftp-server-name = \\\"#{options[:nextServer]}\\\";"
  174. statements << "option bootfile-name = \\\"#{options[:filename]}\\\";"
  175. end
  176. statements
  177. end
  178. def bootServer(server)
  179. ip2hex(validate_ip(server))
  180. rescue
  181. begin
  182. logger.info "Next-server option not IPv4, trying to resolve '#{server}'"
  183. ip2hex(dns_resolv.getaddress(server))
  184. rescue Resolv::ResolvError => e
  185. logger.warn "Unable to resolve PTR query for '#{server}', will use the hostname"
  186. logger.debug "Reason: #{e}"
  187. # use hostname as the next-server entry
  188. "\\\"#{server}\\\""
  189. end
  190. end
  191. def ip2hex(ip)
  192. ip.to_s.split(".").map { |i| "%02x" % i }.join(":")
  193. end
  194. end
  195. end