PageRenderTime 51ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/core/model/vmware_esxi.rb

https://github.com/drasamsetti/Hanlon
Ruby | 354 lines | 330 code | 10 blank | 14 comment | 12 complexity | 0c6a6a997e57f9168efb30e0c5ec9576 MD5 | raw file
Possible License(s): Apache-2.0
  1. require "erb"
  2. # Root ProjectHanlon namespace
  3. module ProjectHanlon
  4. module ModelTemplate
  5. # Root Model object
  6. # @abstract
  7. class VMwareESXi < ProjectHanlon::ModelTemplate::Base
  8. include(ProjectHanlon::Logging)
  9. # Assigned image
  10. attr_accessor :image_uuid
  11. # Metadata
  12. attr_accessor :hostname
  13. # Compatible Image Prefix
  14. attr_accessor :image_prefix
  15. def initialize(hash)
  16. super(hash)
  17. # Static config
  18. @hidden = true
  19. @template = :vmware_hypervisor
  20. @name = "vmware_esx_generic"
  21. @description = "vmware esxi generic"
  22. # Metadata vars
  23. @hostname_prefix = nil
  24. # State / must have a starting state
  25. @current_state = :init
  26. # Image UUID
  27. @image_uuid = true
  28. # Image prefix we can attach
  29. @image_prefix = "esxi"
  30. # Enable agent brokers for this model
  31. @broker_plugin = :proxy
  32. @final_state = :os_complete
  33. # Metadata vars
  34. @esx_license = nil
  35. @ip_range_network = nil
  36. @ip_range_start = nil
  37. @ip_range_end = nil
  38. @gateway = nil
  39. @hostname_prefix = nil
  40. @nameserver = nil
  41. @ntpserver = nil
  42. @vcenter_name = nil
  43. @vcenter_datacenter_path = nil
  44. @vcenter_cluster_path = nil
  45. @enable_vsan = ""
  46. @vsan_uuid = UUID.generate
  47. @packages = []
  48. @configure_disk_to_local = ""
  49. # Metadata
  50. @req_metadata_hash = {
  51. "@esx_license" => { :default => "",
  52. :example => "AAAAA-BBBBB-CCCCC-DDDDD-EEEEE",
  53. :validation => '^[A-Z\d]{5}-[A-Z\d]{5}-[A-Z\d]{5}-[A-Z\d]{5}-[A-Z\d]{5}$',
  54. :required => true,
  55. :description => "ESX License Key" },
  56. "@root_password" => { :default => "test1234",
  57. :example => "P@ssword!",
  58. :validation => '^[\S]{8,}',
  59. :required => true,
  60. :description => "root password (> 8 characters)"
  61. },
  62. "@ip_range_network" => { :default => "",
  63. :example => "192.168.10",
  64. :validation => '^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b$',
  65. :required => true,
  66. :description => "IP Network for hosts" },
  67. "@ip_range_subnet" => { :default => "255.255.255.0",
  68. :example => "255.255.255.0",
  69. :validation => '^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b$',
  70. :required => true,
  71. :description => "IP Subnet" },
  72. "@ip_range_start" => { :default => "",
  73. :example => "1",
  74. :validation => '^\b(25[0-4]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',
  75. :required => true,
  76. :description => "Starting IP address (1-254)" },
  77. "@ip_range_end" => { :default => "",
  78. :example => "50",
  79. :validation => '^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',
  80. :required => true,
  81. :description => "Ending IP address (2-255)" },
  82. "@gateway" => { :default => "",
  83. :example => "192.168.1.1",
  84. :validation => '^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b$',
  85. :required => true,
  86. :description => "Gateway for node" },
  87. "@hostname_prefix" => { :default => "",
  88. :example => "esxi-node",
  89. :validation => '^[A-Za-z\d-]{3,}$',
  90. :required => true,
  91. :description => "Prefix for naming node" },
  92. "@nameserver" => { :default => "",
  93. :example => "192.168.10.10",
  94. :validation => '^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b$',
  95. :required => true,
  96. :description => "Nameserver for node" },
  97. "@ntpserver" => { :default => "",
  98. :example => "ntp.hanlon.example.local",
  99. :validation => '^[\w.]{3,}$',
  100. :required => true,
  101. :description => "NTP server for node" }
  102. }
  103. @opt_metadata_hash = {
  104. "@vcenter_name" => { :default => "",
  105. :example => "vcenter01",
  106. :validation => '^[\w.-]{3,}$',
  107. :required => false,
  108. :description => "Optional for broker use: the vCenter to attach ESXi node to" },
  109. "@vcenter_datacenter_path" => { :default => "",
  110. :example => "Datacenter01",
  111. :validation => '^[a-zA-Z\d-]{3,}$',
  112. :required => false,
  113. :description => "Optional for broker use: the vCenter Datacenter path to place ESXi host in" },
  114. "@vcenter_cluster_path" => { :default => "",
  115. :example => "Cluster01",
  116. :validation => '^[a-zA-Z\d-]{3,}$',
  117. :required => false,
  118. :description => "Optional for broker use: the vCenter Cluster to place ESXi node in" },
  119. "@enable_vsan" => { :default => "False",
  120. :example => "",
  121. :validation => '',
  122. :required => false,
  123. :description => "Join vSAN cluster and create vSAN disk groups" },
  124. "@vsan_uuid" => { :default => "",
  125. :example => "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  126. :validation => '^[a-z\d]{8}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{12}$',
  127. :required => false,
  128. :description => "VMware vSAN UUID. Use the default or type in" },
  129. "@packages" => { :default => "",
  130. :example => "",
  131. :validation => '',
  132. :required => false,
  133. :description => "Optional for broker use: the vCenter Cluster to place ESXi node in" },
  134. "@configure_disk_to_local" => { :default => "False",
  135. :example => "",
  136. :validation => '',
  137. :required => false,
  138. :description => "Optional for vSAN, should we use non-local disks in vSAN disk group." }
  139. }
  140. from_hash(hash) unless hash == nil
  141. end
  142. def node_ip_address
  143. "#{@ip_range_network}.#{(@ip_range_start..@ip_range_end).to_a[@counter - 1]}"
  144. end
  145. def node_hostname
  146. @hostname_prefix + @counter.to_s
  147. end
  148. def broker_proxy_handoff
  149. logger.debug "Broker proxy called for: #{@broker.name}"
  150. if node_ip_address
  151. options = {
  152. :username => "root",
  153. :password => @root_password,
  154. :metadata => node_metadata,
  155. :hostname => node_hostname,
  156. :uuid => @node.uuid,
  157. :ipaddress => node_ip_address,
  158. :vcenter_name => @vcenter_name,
  159. :vcenter_datacenter_path => @vcenter_datacenter_path,
  160. :vcenter_cluster_path => @vcenter_cluster_path,
  161. }
  162. @current_state = @broker.proxy_hand_off(options)
  163. else
  164. logger.error "Node IP address isn't known"
  165. @current_state = :broker_fail
  166. end
  167. broker_fsm_log
  168. end
  169. def callback
  170. {
  171. "broker" => :broker_agent_handoff,
  172. "boot_cfg" => :boot_cfg,
  173. "kickstart" => :kickstart,
  174. "postinstall" => :postinstall,
  175. }
  176. end
  177. def fsm_tree
  178. {
  179. :init => { :mk_call => :init,
  180. :boot_call => :init,
  181. :kickstart_start => :preinstall,
  182. :kickstart_file => :init,
  183. :kickstart_end => :postinstall,
  184. :timeout => :timeout_error,
  185. :error => :error_catch,
  186. :else => :init },
  187. :preinstall => { :mk_call => :preinstall,
  188. :boot_call => :preinstall,
  189. :kickstart_start => :preinstall,
  190. :kickstart_file => :init,
  191. :kickstart_end => :postinstall,
  192. :kickstart_timeout => :timeout_error,
  193. :error => :error_catch,
  194. :else => :preinstall },
  195. :postinstall => { :mk_call => :postinstall,
  196. :boot_call => :postinstall,
  197. :postinstall_end => :os_complete,
  198. :kickstart_file => :postinstall,
  199. :kickstart_end => :postinstall,
  200. :kickstart_timeout => :postinstall,
  201. :error => :error_catch,
  202. :else => :preinstall },
  203. :os_complete => { :mk_call => :os_complete,
  204. :boot_call => :os_complete,
  205. :else => :os_complete,
  206. :reset => :init },
  207. :timeout_error => { :mk_call => :timeout_error,
  208. :boot_call => :timeout_error,
  209. :else => :timeout_error,
  210. :reset => :init },
  211. :error_catch => { :mk_call => :error_catch,
  212. :boot_call => :error_catch,
  213. :else => :error_catch,
  214. :reset => :init },
  215. }
  216. end
  217. def mk_call(node, policy_uuid)
  218. super(node, policy_uuid)
  219. case @current_state
  220. # We need to reboot
  221. when :init, :preinstall, :postinstall, :os_complete
  222. ret = [:reboot, { }]
  223. when :timeout_error, :error_catch
  224. ret = [:acknowledge, { }]
  225. else
  226. ret = [:acknowledge, { }]
  227. end
  228. fsm_action(:mk_call, :mk_call)
  229. ret
  230. end
  231. def boot_call(node, policy_uuid)
  232. super(node, policy_uuid)
  233. case @current_state
  234. when :init, :preinstall
  235. ret = start_install(node, policy_uuid)
  236. when :postinstall, :os_complete, :broker_check, :broker_fail, :broker_success, :complete_no_broker
  237. ret = local_boot(node)
  238. when :timeout_error, :error_catch
  239. engine = ProjectHanlon::Engine.instance
  240. ret = engine.default_mk_boot(node.uuid)
  241. else
  242. engine = ProjectHanlon::Engine.instance
  243. ret = engine.default_mk_boot(node.uuid)
  244. end
  245. fsm_action(:boot_call, :boot_call)
  246. ret
  247. end
  248. def start_install(node, policy_uuid)
  249. ip = "#!ipxe\n"
  250. ip << "echo Reached #{@label} model boot_call\n"
  251. ip << "echo Our image UUID is: #{@image_uuid}\n"
  252. ip << "echo Our state is: #{@current_state}\n"
  253. ip << "echo Our node UUID: #{node.uuid}\n"
  254. ip << "\n"
  255. ip << "echo We will be running an install now\n"
  256. ip << "sleep 3\n"
  257. ip << "\n"
  258. ip << "kernel --name mboot.c32 #{image_svc_uri}/#{@image_uuid}/mboot.c32\n"
  259. ip << "imgargs mboot.c32 -c #{api_svc_uri}/policy/callback/#{policy_uuid}/boot_cfg\n"
  260. ip << "boot\n"
  261. ip
  262. end
  263. def local_boot(node)
  264. ip = "#!ipxe\n"
  265. ip << "echo Reached #{@label} model boot_call\n"
  266. ip << "echo Our image UUID is: #{@image_uuid}\n"
  267. ip << "echo Our state is: #{@current_state}\n"
  268. ip << "echo Our node UUID: #{node.uuid}\n"
  269. ip << "\n"
  270. ip << "echo Continuing local boot\n"
  271. ip << "sleep 3\n"
  272. ip << "\n"
  273. ip << "sanboot --no-describe --drive 0x80\n"
  274. ip
  275. end
  276. def kickstart
  277. @arg = @args_array.shift
  278. case @arg
  279. when "start"
  280. fsm_action(:kickstart_start, :kickstart)
  281. return "ok"
  282. when "end"
  283. fsm_action(:kickstart_end, :kickstart)
  284. return "ok"
  285. when "file"
  286. fsm_action(:kickstart_file, :kickstart)
  287. return kickstart_file
  288. else
  289. return "error"
  290. end
  291. end
  292. def postinstall
  293. @arg = @args_array.shift
  294. case @arg
  295. when "end"
  296. fsm_action(:postinstall_end, :postinstall)
  297. return "ok"
  298. when "debug"
  299. ret = ""
  300. ret << "vcenter: #{@vcenter_name}\n"
  301. ret << "vcenter: #{@vcenter_datacenter_path}\n"
  302. ret << "vcenter: #{@vcenter_cluster_path}\n"
  303. return ret
  304. else
  305. return "error"
  306. end
  307. end
  308. def boot_cfg
  309. @image = get_data.fetch_object_by_uuid(:images, @image_uuid)
  310. if @node.dhcp_mac
  311. @image.boot_cfg.gsub("/",
  312. "#{image_svc_uri}/#{@image_uuid}/").gsub("runweasel",
  313. "ks=#{api_svc_uri}/policy/callback/#{@policy_uuid}/kickstart/file BOOTIF=#{@node.dhcp_mac}")
  314. else
  315. @image.boot_cfg.gsub("/",
  316. "#{image_svc_uri}/#{@image_uuid}/").gsub("runweasel",
  317. "ks=#{api_svc_uri}/policy/callback/#{@policy_uuid}/kickstart/file")
  318. end
  319. end
  320. # ERB.result(binding) is failing in Ruby 1.9.2 and 1.9.3 so template is processed in the def block.
  321. def template_filepath(filename)
  322. raise ProjectHanlon::Error::Slice::InternalError, "must provide esxi version." unless @osversion
  323. filepath = File.join(File.dirname(__FILE__), "esxi/#{@osversion}/#{filename}.erb")
  324. end
  325. def kickstart_file
  326. filepath = template_filepath('kickstart')
  327. ERB.new(File.read(filepath)).result(binding)
  328. end
  329. end
  330. end
  331. end