Guest User

Untitled

a guest
Jun 18th, 2019
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.83 KB | None | 0 0
  1. #!/usr/bin/env ruby
  2. # -------------------------------------------------------------------------- #
  3. # Copyright 2002-2019, OpenNebula Project, OpenNebula Systems #
  4. # #
  5. # Licensed under the Apache License, Version 2.0 (the "License"); you may #
  6. # not use this file except in compliance with the License. You may obtain #
  7. # a copy of the License at #
  8. # #
  9. # http://www.apache.org/licenses/LICENSE-2.0 #
  10. # #
  11. # Unless required by applicable law or agreed to in writing, software #
  12. # distributed under the License is distributed on an "AS IS" BASIS, #
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
  14. # See the License for the specific language governing permissions and #
  15. # limitations under the License. #
  16. # -------------------------------------------------------------------------- #
  17. ONE_LOCATION = ENV["ONE_LOCATION"] if !defined?(ONE_LOCATION)
  18.  
  19. if !ONE_LOCATION
  20. RUBY_LIB_LOCATION = "/usr/lib/one/ruby" if !defined?(RUBY_LIB_LOCATION)
  21. ETC_LOCATION = "/etc/one/" if !defined?(ETC_LOCATION)
  22. else
  23. RUBY_LIB_LOCATION = ONE_LOCATION + "/lib/ruby" if !defined?(RUBY_LIB_LOCATION)
  24. ETC_LOCATION = ONE_LOCATION + "/etc/" if !defined?(ETC_LOCATION)
  25. end
  26.  
  27. # Load credentials and environment
  28. require 'yaml'
  29.  
  30. $: << RUBY_LIB_LOCATION
  31.  
  32. require 'CommandManager'
  33. require 'scripts_common'
  34. require 'rexml/document'
  35. require 'VirtualMachineDriver'
  36. require 'opennebula'
  37.  
  38. # The main class for the driver
  39. class OpenNebulaDriver
  40. ACTION = VirtualMachineDriver::ACTION
  41. POLL_ATTRIBUTE = VirtualMachineDriver::POLL_ATTRIBUTE
  42. VM_STATE = VirtualMachineDriver::VM_STATE
  43.  
  44. LIMIT_DEFAULT = "-1"
  45. LIMIT_UNLIMITED = "-2"
  46.  
  47. UNLIMITED_CPU_VALUE = "100000"
  48. UNLIMITED_MEMORY_VALUE = "1073741824"
  49.  
  50. # Local deploy ids will be formed with this prefix and the remote VM ID
  51. #DEPLOY_ID_PREFIX = "one-"
  52. DEPLOY_ID_PREFIX = "opennebula-hybrid-"
  53.  
  54. # Remote VM names will be formed with this prefix, plus the local VM ID
  55. REMOTE_NAME_PREFIX = "remote-opennebula-"
  56.  
  57. # constructor, loads credentials and endpoint
  58. def initialize(host_name, host_id=nil)
  59. @host_name = host_name
  60. @host_id = host_id
  61.  
  62. client = OpenNebula::Client.new
  63. if host_id.nil?
  64. host_pool = OpenNebula::HostPool.new(client)
  65. host_pool.info
  66. objects=host_pool.select {|object| object.name==host_name }
  67. xmlhost = objects.first
  68. host_id = xmlhost["ID"].to_i
  69. end
  70.  
  71. host = OpenNebula::Host.new_with_id(host_id, client)
  72. host.info
  73.  
  74. region = {}
  75.  
  76. ["user", "password", "endpoint", "capacity"].each do |key|
  77. if key == "capacity"
  78. region[key] = {}
  79. ["cpu", "memory"].each do |key_c|
  80. value = host.retrieve_elements("/HOST/TEMPLATE/ONE_#{key.upcase}/#{key_c.upcase}")[0]
  81. if host.retrieve_elements("/HOST/TEMPLATE/ONE_#{key.upcase}/#{key_c.upcase}")[0].nil? || value == ""
  82. raise "Region for host #{host} does not have '#{key_c.upcase}' defined in host template"
  83. end
  84. region[key][key_c] = value.to_i
  85. end
  86. else
  87. value = host.retrieve_elements("/HOST/TEMPLATE/ONE_#{key.upcase}")[0]
  88. if host.retrieve_elements("/HOST/TEMPLATE/ONE_#{key.upcase}")[0].nil? || value == ""
  89. raise "Region for host #{host} does not have '#{key.upcase}' defined in host template"
  90. end
  91. region[key] = value
  92. end
  93. end
  94.  
  95. system = OpenNebula::System.new(client)
  96. config = system.get_configuration
  97. raise "Error getting oned configuration : #{config.message}" if OpenNebula.is_error?(config)
  98.  
  99. token = config["ONE_KEY"]
  100. conn_opts = {
  101. :password => host["/HOST/TEMPLATE/ONE_PASSWORD"],
  102. }
  103.  
  104. begin
  105. conn_opts = OpenNebula.decrypt(conn_opts, token)
  106. region["password"] = conn_opts[:password]
  107. rescue
  108. raise "HOST: #{host} must have remote host password to work properly"
  109. end
  110.  
  111. secret = "#{region['user']}:#{region['password']}"
  112.  
  113. @client = OpenNebula::Client.new(secret, region['endpoint'], :sync => true)
  114.  
  115. @cpu = region['capacity']['cpu']
  116. @memory = region['capacity']['memory']
  117.  
  118. @cpu = 0 if @cpu.nil?
  119. @memory = 0 if @memory.nil?
  120. end
  121.  
  122. # DEPLOY action, also sets ports and ip if needed
  123. def deploy(id, host, xml_text, lcm_state, deploy_id)
  124. if lcm_state == "BOOT" || lcm_state == "BOOT_FAILURE"
  125. one_info = get_deployment_info(host, xml_text)
  126.  
  127. #load_default_template_values
  128.  
  129. tid = one_value(one_info, 'TEMPLATE_ID')
  130.  
  131. if (tid.nil? || tid == "")
  132. STDERR.puts("Cannot find TEMPLATE_ID in deployment file")
  133. exit(-1)
  134. end
  135.  
  136. extra_template = "REMOTE_OPENNEBULA = YES\n"<<
  137. "REMOTE_OPENNEBULA_VM_ID = #{id}\n"
  138.  
  139. # The OpenNebula context will be included
  140. xml = OpenNebula::XMLElement.new
  141. xml.initialize_xml(xml_text, 'VM')
  142.  
  143. if xml.has_elements?('TEMPLATE/CONTEXT')
  144. # Since there is only 1 level ',' will not be added
  145. context_str = xml.template_like_str('TEMPLATE/CONTEXT')
  146.  
  147. if xml['TEMPLATE/CONTEXT/TOKEN'] == 'YES'
  148. # TODO use OneGate library. See ec2_driver.rb
  149. token_str = generate_onegate_token(xml)
  150. if token_str
  151. context_str << "\nONEGATE_TOKEN=\"#{token_str}\""
  152. end
  153. end
  154.  
  155. extra_template << context_str
  156. end
  157.  
  158. t = OpenNebula::Template.new_with_id(tid, @client)
  159. rc = t.instantiate(REMOTE_NAME_PREFIX+id, true, extra_template, false)
  160.  
  161. if OpenNebula.is_error?(rc)
  162. STDERR.puts(rc.to_str())
  163. exit(-1)
  164. end
  165.  
  166. deploy_id = "#{DEPLOY_ID_PREFIX}#{rc}"
  167. vm = get_remote_vm(deploy_id)
  168.  
  169. if !context_str.nil?
  170. new_context_update = "CONTEXT = [" << context_str <<"]"
  171. new_context_update = new_context_update.gsub("\n", ",\n")
  172. rc = vm.updateconf(new_context_update)
  173. end
  174.  
  175. if OpenNebula.is_error?(rc)
  176. STDERR.puts(rc.to_str())
  177. exit(-1)
  178. end
  179.  
  180. vm.release
  181.  
  182. rc = vm.update("REMOTE_OPENNEBULA_DEPLOY_ID = \"#{deploy_id}\"", true)
  183.  
  184. if OpenNebula.is_error?(rc)
  185. STDERR.puts("Error adding REMOTE_OPENNEBULA_DEPLOY_ID attribute to VM #{rc}: #{rc.to_str()}")
  186. end
  187.  
  188. puts(deploy_id)
  189. else
  190. restore(deploy_id)
  191. deploy_id
  192. end
  193. end
  194.  
  195. # Shutdown an instance
  196. def shutdown(deploy_id, lcm_state)
  197. vm = get_remote_vm(deploy_id)
  198.  
  199. case lcm_state
  200. when "SHUTDOWN"
  201. rc = vm.terminate
  202. when "SHUTDOWN_POWEROFF"
  203. rc = vm.poweroff
  204. when "SHUTDOWN_UNDEPLOY"
  205. rc = vm.undeploy
  206. end
  207.  
  208. if OpenNebula.is_error?(rc)
  209. STDERR.puts(rc.to_str())
  210. exit(-1)
  211. end
  212. end
  213.  
  214. # Reboot an instance
  215. def reboot(deploy_id)
  216. vm = get_remote_vm(deploy_id)
  217. rc = vm.reboot()
  218.  
  219. if OpenNebula.is_error?(rc)
  220. STDERR.puts(rc.to_str())
  221. exit(-1)
  222. end
  223. end
  224.  
  225. # Reboot (hard) an instance
  226. def reset(deploy_id)
  227. vm = get_remote_vm(deploy_id)
  228. rc = vm.reboot(true)
  229.  
  230. if OpenNebula.is_error?(rc)
  231. STDERR.puts(rc.to_str())
  232. exit(-1)
  233. end
  234. end
  235.  
  236. # Cancel an instance
  237. def cancel(deploy_id, lcm_state)
  238. vm = get_remote_vm(deploy_id)
  239.  
  240. case lcm_state
  241. when "SHUTDOWN"
  242. rc = vm.terminate(true)
  243. when "SHUTDOWN_POWEROFF"
  244. rc = vm.poweroff(true)
  245. when "SHUTDOWN_UNDEPLOY"
  246. rc = vm.undeploy(true)
  247. end
  248.  
  249. if OpenNebula.is_error?(rc)
  250. STDERR.puts(rc.to_str())
  251. exit(-1)
  252. end
  253. end
  254.  
  255. # Save an instance
  256. def save(deploy_id)
  257. vm = get_remote_vm(deploy_id)
  258.  
  259. rc = vm.suspend()
  260.  
  261. if OpenNebula.is_error?(rc)
  262. STDERR.puts(rc.to_str())
  263. exit(-1)
  264. end
  265. end
  266.  
  267. # Resumes an instance
  268. def restore(deploy_id)
  269. vm = get_remote_vm(deploy_id)
  270. rc = vm.resume()
  271.  
  272. if OpenNebula.is_error?(rc)
  273. STDERR.puts(rc.to_str())
  274. exit(-1)
  275. end
  276. end
  277.  
  278. # Get info (IP, and state) for an instance
  279. def poll(id, deploy_id)
  280. vm = get_remote_vm(deploy_id)
  281. rc = vm.info
  282.  
  283. if OpenNebula.is_error?(rc)
  284. STDERR.puts(rc.to_str())
  285. exit(-1)
  286. end
  287.  
  288. puts parse_poll(vm)
  289. end
  290.  
  291. # Get the info of all the remote instances
  292. def monitor_all_vms
  293. user = OpenNebula::User.new_with_id("-1", @client)
  294. rc = user.info
  295.  
  296. if OpenNebula.is_error?(rc)
  297. STDERR.puts("Error getting remote user information: #{rc.to_str()}")
  298. exit(-1)
  299. end
  300.  
  301. group = OpenNebula::Group.new_with_id("-1", @client)
  302. rc = group.info
  303.  
  304. if OpenNebula.is_error?(rc)
  305. STDERR.puts("Error getting remote group information: #{rc.to_str()}")
  306. exit(-1)
  307. end
  308.  
  309. if @cpu != 0
  310. totalcpu = @cpu
  311. else
  312. u_cpu = user['VM_QUOTA/VM/CPU']
  313. g_cpu = group['VM_QUOTA/VM/CPU']
  314.  
  315. if u_cpu == LIMIT_DEFAULT || u_cpu.nil?
  316. u_cpu = user['DEFAULT_USER_QUOTAS/VM_QUOTA/VM/CPU']
  317. end
  318.  
  319. if g_cpu == LIMIT_DEFAULT || g_cpu.nil?
  320. g_cpu = group['DEFAULT_GROUP_QUOTAS/VM_QUOTA/VM/CPU']
  321. end
  322.  
  323. u_cpu = LIMIT_UNLIMITED if u_cpu.nil?
  324. g_cpu = LIMIT_UNLIMITED if g_cpu.nil?
  325.  
  326. u_cpu = UNLIMITED_CPU_VALUE if u_cpu == LIMIT_UNLIMITED
  327. g_cpu = UNLIMITED_CPU_VALUE if g_cpu == LIMIT_UNLIMITED
  328.  
  329. totalcpu = ([u_cpu.to_f, g_cpu.to_f].min * 100).round
  330. end
  331.  
  332. if @memory != 0
  333. totalmemory = @memory
  334. else
  335. u_memory = user['VM_QUOTA/VM/MEMORY']
  336. g_memory = group['VM_QUOTA/VM/MEMORY']
  337.  
  338. if u_memory == LIMIT_DEFAULT || u_memory.nil?
  339. u_memory = user['DEFAULT_USER_QUOTAS/VM_QUOTA/VM/MEMORY']
  340. end
  341.  
  342. if g_memory == LIMIT_DEFAULT || g_memory.nil?
  343. g_memory = group['DEFAULT_GROUP_QUOTAS/VM_QUOTA/VM/MEMORY']
  344. end
  345.  
  346. u_memory = LIMIT_UNLIMITED if u_memory.nil?
  347. g_memory = LIMIT_UNLIMITED if g_memory.nil?
  348.  
  349. u_memory = UNLIMITED_MEMORY_VALUE if u_memory == LIMIT_UNLIMITED
  350. g_memory = UNLIMITED_MEMORY_VALUE if g_memory == LIMIT_UNLIMITED
  351.  
  352. totalmemory = [u_memory.to_i, g_memory.to_i].min
  353. end
  354.  
  355. host_info = "HYPERVISOR=opennebula\n"
  356. host_info << "PUBLIC_CLOUD=YES\n"
  357. host_info << "PRIORITY=-1\n"
  358. host_info << "TOTALMEMORY=#{totalmemory * 1024}\n"
  359. host_info << "TOTALCPU=#{totalcpu}\n"
  360. host_info << "HOSTNAME=\"#{@host_name}\"\n"
  361.  
  362. vms_info = "VM_POLL=YES\n"
  363.  
  364. if user['VM_QUOTA/VM/CPU_USED'].nil?
  365. usedcpu = 0
  366. else
  367. usedcpu = (user['VM_QUOTA/VM/CPU_USED'].to_f * 100).round
  368. end
  369.  
  370. if user['VM_QUOTA/VM/MEMORY_USED'].nil?
  371. usedmemory = 0
  372. else
  373. usedmemory = user['VM_QUOTA/VM/MEMORY_USED'].to_i * 1024
  374. end
  375.  
  376. vmpool = OpenNebula::VirtualMachinePool.new(@client,
  377. OpenNebula::VirtualMachinePool::INFO_ALL_VM)
  378. rc = vmpool.info
  379.  
  380. if OpenNebula.is_error?(rc)
  381. STDERR.puts(rc.to_str())
  382. exit(-1)
  383. end
  384.  
  385. vmpool.each do |vm|
  386. poll_data = parse_poll(vm)
  387.  
  388. deploy_id = vm["USER_TEMPLATE/REMOTE_OPENNEBULA_DEPLOY_ID"] || "#{DEPLOY_ID_PREFIX}#{vm.id()}"
  389. vmid = vm["USER_TEMPLATE/REMOTE_OPENNEBULA_VM_ID"] || "-1"
  390.  
  391. vm_template_to_one = vm_to_import(vm)
  392. vm_template_to_one = Base64.encode64(vm_template_to_one).gsub("\n","")
  393.  
  394. vms_info << "VM=[\n"
  395. vms_info << " ID=\"#{vmid}\",\n"
  396. vms_info << " DEPLOY_ID=\"#{deploy_id}\",\n"
  397. vms_info << " VM_NAME=#{vm.name},\n"
  398. vms_info << " IMPORT_TEMPLATE=\"#{vm_template_to_one}\",\n"
  399. vms_info << " POLL=\"#{poll_data}\" ]\n"
  400. end
  401.  
  402. host_info << "USEDMEMORY=#{usedmemory}\n"
  403. host_info << "USEDCPU=#{usedcpu}\n"
  404. host_info << "FREEMEMORY=#{(totalmemory - usedmemory)}\n"
  405. host_info << "FREECPU=#{(totalcpu - usedcpu)}\n"
  406.  
  407. puts host_info
  408. puts vms_info
  409. end
  410.  
  411. private
  412.  
  413. # Get the OpenNebula hybrid section of the template. With more than one section
  414. # the HOST element is used and matched with the host
  415. def get_deployment_info(host, xml_text)
  416. xml = REXML::Document.new xml_text
  417.  
  418. one = nil
  419.  
  420. all_one_elements = xml.root.get_elements("//USER_TEMPLATE/PUBLIC_CLOUD")
  421.  
  422. # First, let's see if we have an one site that matches
  423. # our desired host name
  424. all_one_elements.each { |element|
  425. cloud = element.elements["HOST"]
  426. if cloud && cloud.text.upcase == host.upcase
  427. one = element
  428. end
  429. }
  430.  
  431. if !one
  432. # If we don't find the one site, and ONE just
  433. # knows about one one site, let's use that
  434. if all_one_elements.size == 1
  435. one = all_one_elements[0]
  436. else
  437. STDERR.puts("Cannot find PUBLIC_CLOUD element in deployment "\
  438. " file or no HOST site matching the requested in the "\
  439. "template.")
  440. exit(-1)
  441. end
  442. end
  443.  
  444. one
  445. end
  446.  
  447. # Retrieve the vm information from the instance
  448. def parse_poll(vm)
  449. begin
  450. vm_hash = vm.to_hash()
  451.  
  452. state = ""
  453.  
  454. state = case vm.state_str
  455. when "INIT" || "PENDING" || "HOLD" || "CLONING"
  456. VM_STATE[:active]
  457. when "ACTIVE"
  458. case vm.lcm_state_str
  459. when /_FAILURE$/ || "UNKNOWN"
  460. VM_STATE[:error]
  461. else
  462. VM_STATE[:active]
  463. end
  464. when "STOPPED" || "SUSPENDED"
  465. VM_STATE[:paused]
  466. when "DONE" || "POWEROFF" || "UNDEPLOYED"
  467. VM_STATE[:deleted]
  468. when "FAILED" || "CLONING_FAILURE"
  469. VM_STATE[:error]
  470. else
  471. VM_STATE[:unknown]
  472. end
  473.  
  474. info = "#{POLL_ATTRIBUTE[:state]}=#{state} "
  475.  
  476. if state != VM_STATE[:active]
  477. return info
  478. end
  479.  
  480. monitoring = vm_hash['VM']['MONITORING']
  481.  
  482. if (!monitoring.nil?)
  483. info << vm.template_like_str('MONITORING', true).gsub("\n", " ").gsub('"', '').gsub(/\[ */, "[").gsub(/, */,",").gsub(/ *\]/, "]").gsub(/STATE=./,"")
  484. end
  485.  
  486. return info
  487. rescue
  488. # Unkown state if exception occurs retrieving information from
  489. # an instance
  490. "#{POLL_ATTRIBUTE[:state]}=#{VM_STATE[:unknown]} "
  491. end
  492. end
  493.  
  494. # Returns the value of the xml specified by the name or the default
  495. # one if it does not exist
  496. # +xml+: REXML Document, containing one hybrid information
  497. # +name+: String, xpath expression to retrieve the value
  498. # +block+: Block, block to be applied to the value before returning it
  499. def one_value(xml, name, &block)
  500. value = value_from_xml(xml, name) || @defaults[name]
  501. if block_given? && value
  502. block.call(value)
  503. else
  504. value
  505. end
  506. end
  507.  
  508. def value_from_xml(xml, name)
  509. if xml
  510. element = xml.elements[name]
  511. element.text.strip if element && element.text
  512. end
  513. end
  514.  
  515. # Load the default values that will be used to create a new instance, if
  516. # not provided in the template. These values are defined in the
  517. # ONE_DRIVER_DEFAULT file
  518. def load_default_template_values
  519. @defaults = Hash.new
  520.  
  521. if File.exists?(ONE_DRIVER_DEFAULT)
  522. fd = File.new(ONE_DRIVER_DEFAULT)
  523. xml = REXML::Document.new fd
  524. fd.close()
  525.  
  526. return if !xml || !xml.root
  527.  
  528. xml.elements.each("/TEMPLATE/PUBLIC_CLOUD/*") do |e|
  529. @defaults[e.name] = e.text
  530. end
  531. end
  532. end
  533.  
  534. # Retrive the vm object for the remote opennebula
  535. def get_remote_vm(deploy_id)
  536. begin
  537. match = deploy_id.match( /#{DEPLOY_ID_PREFIX}(.*)/ )
  538.  
  539. if (match.nil?)
  540. raise "Deploy ID #{deploy_id} was not created with this driver"
  541. end
  542.  
  543. id = match[1]
  544.  
  545. return OpenNebula::VirtualMachine.new_with_id(id, @client)
  546. rescue => e
  547. STDERR.puts e.message
  548. exit(-1)
  549. end
  550. end
  551.  
  552. # Build template for importation
  553. def vm_to_import(vm)
  554. cpu = vm['TEMPLATE/CPU']
  555. vcpu = vm['TEMPLATE/VCPU']
  556. mem = vm['TEMPLATE/MEMORY']
  557.  
  558. template_id = vm['TEMPLATE/TEMPLATE_ID']
  559.  
  560. deploy_id = "#{DEPLOY_ID_PREFIX}#{vm.id()}"
  561.  
  562. str = "NAME = \"Instance from #{vm.name()}\"\n"\
  563. "CPU = \"#{cpu}\"\n"\
  564. "VCPU = \"#{1}\"\n"\
  565. "MEMORY = \"#{mem}\"\n"\
  566. "HYPERVISOR = \"opennebula\"\n"\
  567. "PUBLIC_CLOUD = [\n"\
  568. " TYPE =\"opennebula\",\n"\
  569. " TEMPLATE_ID =\"#{template_id}\"\n"\
  570. "]\n"\
  571. "IMPORT_VM_ID = \"#{deploy_id}\"\n"\
  572. "SCHED_REQUIREMENTS=\"NAME=\\\"#{@host_name}\\\"\"\n"\
  573. "DESCRIPTION = \"Instance imported from a remote OpenNebula, from VM instance"\
  574. " #{vm.id()}\"\n"
  575.  
  576. str
  577. end
  578.  
  579. # TODO move this method to a OneGate library. See ec2_driver.rb
  580. def generate_onegate_token(xml)
  581. # Create the OneGate token string
  582. vmid_str = xml["ID"]
  583. stime_str = xml["STIME"]
  584. str_to_encrypt = "#{vmid_str}:#{stime_str}"
  585.  
  586. user_id = xml['TEMPLATE/CREATED_BY']
  587.  
  588. if user_id.nil?
  589. STDERR.puts {"VMID:#{vmid} CREATED_BY not present" \
  590. " in the VM TEMPLATE"}
  591. return nil
  592. end
  593.  
  594. user = OpenNebula::User.new_with_id(user_id,
  595. OpenNebula::Client.new)
  596. rc = user.info
  597.  
  598. if OpenNebula.is_error?(rc)
  599. STDERR.puts {"VMID:#{vmid} user.info" \
  600. " error: #{rc.message}"}
  601. return nil
  602. end
  603.  
  604. token_password = user['TEMPLATE/TOKEN_PASSWORD']
  605.  
  606. if token_password.nil?
  607. STDERR.puts {"VMID:#{vmid} TOKEN_PASSWORD not present"\
  608. " in the USER:#{user_id} TEMPLATE"}
  609. return nil
  610. end
  611.  
  612. cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
  613. cipher.encrypt
  614. cipher.key = token_password
  615. onegate_token = cipher.update(str_to_encrypt)
  616. onegate_token << cipher.final
  617.  
  618. onegate_token_64 = Base64.encode64(onegate_token).chop
  619. end
  620. end
Add Comment
Please, Sign In to add comment