From 3c4629953a57cc1529d65e83ca2c07b207a48a00 Mon Sep 17 00:00:00 2001 From: Alejandro Huertas Herrero Date: Mon, 22 Apr 2019 13:32:48 +0200 Subject: [PATCH] B #3082: FSCK doesn't update running quotas * Add function to calculate running quotas * Divide the code in small functions * Refactor some code --- share/linters/.rubocop.yml | 1 - src/onedb/fsck.rb | 12 +- src/onedb/fsck/quotas.rb | 774 +++++++++++++++++++++---------------- 3 files changed, 441 insertions(+), 346 deletions(-) diff --git a/share/linters/.rubocop.yml b/share/linters/.rubocop.yml index 57461f6b9e..d80f6d3dc8 100644 --- a/share/linters/.rubocop.yml +++ b/share/linters/.rubocop.yml @@ -498,7 +498,6 @@ AllCops: - src/onedb/database_schema.rb - src/onedb/fsck/image.rb - src/onedb/fsck/datastore.rb - - src/onedb/fsck/quotas.rb - src/onedb/fsck/history.rb - src/onedb/fsck/vrouter.rb - src/onedb/fsck/pool_control.rb diff --git a/src/onedb/fsck.rb b/src/onedb/fsck.rb index d9edf1c6c1..eb7e1800d5 100644 --- a/src/onedb/fsck.rb +++ b/src/onedb/fsck.rb @@ -462,17 +462,20 @@ EOT # USER QUOTAS ######################################################################## - check_fix_user_quotas + check_fix_quotas('user') + + check_fix_quotas('user', 'running') log_time - ######################################################################## # Groups # # GROUP QUOTAS ######################################################################## - check_fix_group_quotas + check_fix_quotas('group') + + check_fix_quotas('group', 'running') log_time @@ -483,9 +486,8 @@ EOT ######################################################################## check_template - fix_template - log_time + fix_template log_total_errors diff --git a/src/onedb/fsck/quotas.rb b/src/onedb/fsck/quotas.rb index bbc490be79..7903f92651 100644 --- a/src/onedb/fsck/quotas.rb +++ b/src/onedb/fsck/quotas.rb @@ -1,368 +1,150 @@ - +# Quotas module module OneDBFsck - def check_fix_user_quotas - # This block is not needed for now -=begin - @db.transaction do - @db.fetch("SELECT oid FROM user_pool") do |row| - found = false - @db.fetch("SELECT user_oid FROM user_quotas WHERE user_oid=#{row[:oid]}") do |q_row| - found = true - end + # Check and fix quotas + # + # @param resource [String] user/group + # @param type [String] normal/running + def check_fix_quotas(resource, type = 'normal') + table = "#{resource}_quotas" - if !found - log_error("User #{row[:oid]} does not have a quotas entry") + @db.run "ALTER TABLE #{table} RENAME TO old_#{table};" - @db.run "INSERT INTO user_quotas VALUES(#{row[:oid]},'#{row[:oid]}');" - end - end - end -=end - @db.run "ALTER TABLE user_quotas RENAME TO old_user_quotas;" - create_table(:user_quotas) + create_table(table.to_sym) @db.transaction do + query = "SELECT * FROM old_#{table} WHERE #{resource}_oid=0" + # oneadmin does not have quotas - @db.fetch("SELECT * FROM old_user_quotas WHERE user_oid=0") do |row| - @db[:user_quotas].insert(row) + @db.fetch(query) do |row| + @db[table.to_sym].insert(row) end - @db.fetch("SELECT * FROM old_user_quotas WHERE user_oid>0") do |row| - doc = Nokogiri::XML(row[:body],nil,NOKOGIRI_ENCODING){|c| c.default_xml.noblanks} + query = "SELECT * FROM old_#{table} WHERE #{resource}_oid>0" - calculate_quotas(doc, "uid=#{row[:user_oid]}", "User") - - @db[:user_quotas].insert( - :user_oid => row[:user_oid], - :body => doc.root.to_s) - end - end - - @db.run "DROP TABLE old_user_quotas;" - end - - def check_fix_group_quotas - # This block is not needed for now -=begin - @db.transaction do - @db.fetch("SELECT oid FROM group_pool") do |row| - found = false - - @db.fetch("SELECT group_oid FROM group_quotas WHERE group_oid=#{row[:oid]}") do |q_row| - found = true + @db.fetch(query) do |row| + doc = Nokogiri::XML(row[:body], nil, NOKOGIRI_ENCODING) do |c| + c.default_xml.noblanks end - if !found - log_error("Group #{row[:oid]} does not have a quotas entry") + # resource[0] = u if user, g if group + id_field = "#{resource[0]}id" + oid = row["#{resource}_oid".to_sym] + params = [doc, "#{id_field}=#{oid}", resource.capitalize] - @db.run "INSERT INTO group_quotas VALUES(#{row[:oid]},'#{row[:oid]}');" - end - end - end -=end - @db.run "ALTER TABLE group_quotas RENAME TO old_group_quotas;" - create_table(:group_quotas) - - @db.transaction do - # oneadmin does not have quotas - @db.fetch("SELECT * FROM old_group_quotas WHERE group_oid=0") do |row| - @db[:group_quotas].insert(row) - end - - @db.fetch("SELECT * FROM old_group_quotas WHERE group_oid>0") do |row| - doc = Nokogiri::XML(row[:body],nil,NOKOGIRI_ENCODING){|c| c.default_xml.noblanks} - - calculate_quotas(doc, "gid=#{row[:group_oid]}", "Group") - - @db[:group_quotas].insert( - :group_oid => row[:group_oid], - :body => doc.root.to_s) - end - end - - @db.run "DROP TABLE old_group_quotas;" - end - - def calculate_quotas(doc, where_filter, resource) - oid = doc.root.at_xpath("ID").text.to_i - - # VM quotas - cpu_used = 0 - mem_used = 0 - vms_used = 0 - sys_used = 0 - - # VNet quotas - vnet_usage = {} - - # Image quotas - img_usage = {} - datastore_usage = {} - - @db.fetch("SELECT body FROM vm_pool WHERE #{where_filter} AND state<>6") do |vm_row| - vmdoc = nokogiri_doc(vm_row[:body]) - - # VM quotas - vmdoc.root.xpath("TEMPLATE/CPU").each { |e| - # truncate to 2 decimals - cpu = (e.text.to_f * 100).to_i - cpu_used += cpu - } - - vmdoc.root.xpath("TEMPLATE/MEMORY").each { |e| - mem_used += e.text.to_i - } - - vmdoc.root.xpath("TEMPLATE/DISK").each { |e| - type = "" - - e.xpath("TYPE").each { |t_elem| - type = t_elem.text.upcase - } - - size = 0 - - if !e.at_xpath("SIZE").nil? - size = e.at_xpath("SIZE").text.to_i - end - - if ( type == "SWAP" || type == "FS") - sys_used += size + if type == 'running' + calculate_running_quotas(*params) else - if !e.at_xpath("CLONE").nil? - clone = (e.at_xpath("CLONE").text.upcase == "YES") - - target = nil - - if clone - target = e.at_xpath("CLONE_TARGET").text if !e.at_xpath("CLONE_TARGET").nil? - else - target = e.at_xpath("LN_TARGET").text if !e.at_xpath("LN_TARGET").nil? - end - - if !target.nil? && target == "SYSTEM" - sys_used += size - - if !e.at_xpath("DISK_SNAPSHOT_TOTAL_SIZE").nil? - sys_used += e.at_xpath("DISK_SNAPSHOT_TOTAL_SIZE").text.to_i - end - elsif !target.nil? && target == "SELF" - datastore_id = e.at_xpath("DATASTORE_ID").text - datastore_usage[datastore_id] ||= 0 - datastore_usage[datastore_id] += size - - if !e.at_xpath("DISK_SNAPSHOT_TOTAL_SIZE").nil? - datastore_usage[datastore_id] += e.at_xpath("DISK_SNAPSHOT_TOTAL_SIZE").text.to_i - end - end - end + calculate_quotas(*params) end - } - vms_used += 1 + object = { "#{resource}_oid".to_sym => oid, + :body => doc.root.to_s } - # VNet quotas - vmdoc.root.xpath("TEMPLATE/NIC/NETWORK_ID").each { |e| - vnet_usage[e.text] = 0 if vnet_usage[e.text].nil? - vnet_usage[e.text] += 1 - } - - # Image quotas - vmdoc.root.xpath("TEMPLATE/DISK/IMAGE_ID").each { |e| - img_usage[e.text] = 0 if img_usage[e.text].nil? - img_usage[e.text] += 1 - } + @db["#{resource}_quotas".to_sym].insert(object) + end end - @db.fetch("SELECT body FROM vrouter_pool WHERE #{where_filter}") do |vrouter_row| - vrouter_doc = Nokogiri::XML(vrouter_row[:body],nil,NOKOGIRI_ENCODING){|c| c.default_xml.noblanks} - - # VNet quotas - vrouter_doc.root.xpath("TEMPLATE/NIC").each { |nic| - net_id = nil - nic.xpath("NETWORK_ID").each do |nid| - net_id = nid.text - end - - floating = false - - nic.xpath("FLOATING_IP").each do |floating_e| - floating = (floating_e.text.upcase == "YES") - end - - if !net_id.nil? && floating - vnet_usage[net_id] = 0 if vnet_usage[net_id].nil? - - vnet_usage[net_id] += 1 - end - } - end + @db.run "DROP TABLE old_#{table};" + end + # Calculate normal quotas + # + # @param doc [Nokogiri::XML] xml document with all information + # @param filter [String] filter for where clause + # @param resource [String] OpenNebula object + def calculate_quotas(doc, filter, resource) # VM quotas + query = "SELECT body FROM vm_pool WHERE #{filter} AND state<>6" - vm_elem = nil - doc.root.xpath("VM_QUOTA/VM").each { |e| vm_elem = e } + resources = { :cpu => 'CPU', :mem => 'MEMORY', :vms => 'VMS' } - if vm_elem.nil? - doc.root.xpath("VM_QUOTA").each { |e| e.remove } + vm_elem = calculate_vm_quotas(doc, query, resource, resources) - vm_quota = doc.root.add_child(doc.create_element("VM_QUOTA")) - vm_elem = vm_quota.add_child(doc.create_element("VM")) + # System quotas + query = "SELECT body FROM vm_pool WHERE #{filter} AND state<>6" - vm_elem.add_child(doc.create_element("CPU")).content = "-1" - vm_elem.add_child(doc.create_element("CPU_USED")).content = "0" - - vm_elem.add_child(doc.create_element("MEMORY")).content = "-1" - vm_elem.add_child(doc.create_element("MEMORY_USED")).content = "0" - - vm_elem.add_child(doc.create_element("VMS")).content = "-1" - vm_elem.add_child(doc.create_element("VMS_USED")).content = "0" - - vm_elem.add_child(doc.create_element("SYSTEM_DISK_SIZE")).content = "-1" - vm_elem.add_child(doc.create_element("SYSTEM_DISK_SIZE_USED")).content = "0" - end - - vm_elem.xpath("CPU_USED").each { |e| - # Because of bug http://dev.opennebula.org/issues/1567 the element - # may contain a float number in scientific notation. - - # Check if the float value or the string representation mismatch, - # but ignoring the precision - - cpu_used = (cpu_used / 100.0) - - different = ( e.text.to_f != cpu_used || - ![sprintf('%.2f', cpu_used), sprintf('%.1f', cpu_used), sprintf('%.0f', cpu_used)].include?(e.text) ) - - cpu_used_str = sprintf('%.2f', cpu_used) - - if different - log_error("#{resource} #{oid} quotas: CPU_USED has #{e.text} \tis\t#{cpu_used_str}") - e.content = cpu_used_str - end - } - - vm_elem.xpath("MEMORY_USED").each { |e| - if e.text != mem_used.to_s - log_error("#{resource} #{oid} quotas: MEMORY_USED has #{e.text} \tis\t#{mem_used}") - e.content = mem_used.to_s - end - } - - vm_elem.xpath("VMS_USED").each { |e| - if e.text != vms_used.to_s - log_error("#{resource} #{oid} quotas: VMS_USED has #{e.text} \tis\t#{vms_used}") - e.content = vms_used.to_s - end - } - - vm_elem.xpath("SYSTEM_DISK_SIZE_USED").each { |e| - if e.text != sys_used.to_s - log_error("#{resource} #{oid} quotas: SYSTEM_DISK_SIZE_USED has #{e.text} \tis\t#{sys_used}") - e.content = sys_used.to_s - end - } + datastore_usage = calculate_system_quotas(doc, query, resource, vm_elem) # VNet quotas + queries = [] - net_quota = nil - doc.root.xpath("NETWORK_QUOTA").each { |e| net_quota = e } + queries << "SELECT body FROM vm_pool WHERE #{filter} AND state<>6" + queries << "SELECT body FROM vrouter_pool WHERE #{filter}" - if net_quota.nil? - net_quota = doc.root.add_child(doc.create_element("NETWORK_QUOTA")) - end - - net_quota.xpath("NETWORK").each { |net_elem| - vnet_id = net_elem.at_xpath("ID").text - - leases_used = vnet_usage.delete(vnet_id) - - leases_used = 0 if leases_used.nil? - - net_elem.xpath("LEASES_USED").each { |e| - if e.text != leases_used.to_s - log_error("#{resource} #{oid} quotas: VNet #{vnet_id}\tLEASES_USED has #{e.text} \tis\t#{leases_used}") - e.content = leases_used.to_s - end - } - } - - vnet_usage.each { |vnet_id, leases_used| - log_error("#{resource} #{oid} quotas: VNet #{vnet_id}\tLEASES_USED has 0 \tis\t#{leases_used}") - - new_elem = net_quota.add_child(doc.create_element("NETWORK")) - - new_elem.add_child(doc.create_element("ID")).content = vnet_id - new_elem.add_child(doc.create_element("LEASES")).content = "-1" - new_elem.add_child(doc.create_element("LEASES_USED")).content = leases_used.to_s - } + calculate_vnet_quotas(doc, queries, resource) # Image quotas + query = "SELECT body FROM vm_pool WHERE #{filter} AND state<>6" - img_quota = nil - doc.root.xpath("IMAGE_QUOTA").each { |e| img_quota = e } - - if img_quota.nil? - img_quota = doc.root.add_child(doc.create_element("IMAGE_QUOTA")) - end - - img_quota.xpath("IMAGE").each { |img_elem| - img_id = img_elem.at_xpath("ID").text - - rvms = img_usage.delete(img_id) - - rvms = 0 if rvms.nil? - - img_elem.xpath("RVMS_USED").each { |e| - if e.text != rvms.to_s - log_error("#{resource} #{oid} quotas: Image #{img_id}\tRVMS has #{e.text} \tis\t#{rvms}") - e.content = rvms.to_s - end - } - } - - img_usage.each { |img_id, rvms| - log_error("#{resource} #{oid} quotas: Image #{img_id}\tRVMS has 0 \tis\t#{rvms}") - - new_elem = img_quota.add_child(doc.create_element("IMAGE")) - - new_elem.add_child(doc.create_element("ID")).content = img_id - new_elem.add_child(doc.create_element("RVMS")).content = "-1" - new_elem.add_child(doc.create_element("RVMS_USED")).content = rvms.to_s - } + calculate_image_quotas(doc, query, resource) # Datastore quotas + query = "SELECT body FROM image_pool WHERE #{filter}" + + calculate_ds_quotas(doc, query, resource, datastore_usage) + end + + # Calculate running quotas + # + # @param doc [Nokogiri::XML] xml document with all information + # @param filter [String] filter for where clause + # @param resource [String] OpenNebula object + def calculate_running_quotas(doc, filter, resource) + running_states = '(state = 1 OR state = 2 OR state = 3 or state = 10)' + + query = "SELECT body FROM vm_pool WHERE #{filter} AND #{running_states}" + + resources = { :cpu => 'RUNNING_CPU', + :mem => 'RUNNING_MEMORY', + :vms => 'RUNNING_VMS' } + + calculate_vm_quotas(doc, query, resource, resources) + end + + # Calculate datastore quotas + # + # @param doc [Nokogiri::XML] xml document with all information + # @param query [String] database query + # @param resource [String] OpenNebula object + # @param datastore_usage [Object] object with datastore usage information + def calculate_ds_quotas(doc, query, resource, datastore_usage) + oid = doc.root.at_xpath('ID').text.to_i + ds_usage = {} - @db.fetch("SELECT body FROM image_pool WHERE #{where_filter}") do |img_row| - img_doc = Nokogiri::XML(img_row[:body],nil,NOKOGIRI_ENCODING){|c| c.default_xml.noblanks} + @db.fetch(query) do |img_row| + img_doc = Nokogiri::XML(img_row[:body], + nil, + NOKOGIRI_ENCODING) do |c| + c.default_xml.noblanks + end - img_doc.root.xpath("DATASTORE_ID").each { |e| - ds_usage[e.text] = [0,0] if ds_usage[e.text].nil? + img_doc.root.xpath('DATASTORE_ID').each do |e| + ds_usage[e.text] = [0, 0] if ds_usage[e.text].nil? ds_usage[e.text][0] += 1 - img_doc.root.xpath("SIZE").each { |size| + img_doc.root.xpath('SIZE').each do |size| ds_usage[e.text][1] += size.text.to_i - } + end - img_doc.root.xpath("SNAPSHOTS/SNAPSHOT/SIZE").each { |size| + img_doc.root.xpath('SNAPSHOTS/SNAPSHOT/SIZE').each do |size| ds_usage[e.text][1] += size.text.to_i - } - } + end + end end ds_quota = nil - doc.root.xpath("DATASTORE_QUOTA").each { |e| ds_quota = e } + doc.root.xpath('DATASTORE_QUOTA').each {|e| ds_quota = e } if ds_quota.nil? - ds_quota = doc.root.add_child(doc.create_element("DATASTORE_QUOTA")) + ds_quota = doc.root.add_child(doc.create_element('DATASTORE_QUOTA')) end - ds_quota.xpath("DATASTORE").each { |ds_elem| - ds_id = ds_elem.at_xpath("ID").text + ds_quota.xpath('DATASTORE').each do |ds_elem| + ds_id = ds_elem.at_xpath('ID').text - images_used,size_used = ds_usage.delete(ds_id) + images_used, size_used = ds_usage.delete(ds_id) images_used = 0 if images_used.nil? size_used = 0 if size_used.nil? @@ -370,37 +152,349 @@ module OneDBFsck cloned_usage = datastore_usage[ds_id] || 0 size_used += cloned_usage - ds_elem.xpath("IMAGES_USED").each { |e| - if e.text != images_used.to_s - log_error("#{resource} #{oid} quotas: Datastore #{ds_id}\tIMAGES_USED has #{e.text} \tis\t#{images_used}") - e.content = images_used.to_s - end - } + ds_elem.xpath('IMAGES_USED').each do |e| + next if e.text == images_used.to_s - ds_elem.xpath("SIZE_USED").each { |e| - if e.text != size_used.to_s - log_error("#{resource} #{oid} quotas: Datastore #{ds_id}\tSIZE_USED has #{e.text} \tis\t#{size_used}") - e.content = size_used.to_s - end - } - } + log_error("#{resource} #{oid} quotas: Datastore " \ + "#{ds_id}\tIMAGES_USED has #{e.text} " \ + "\tis\t#{images_used}") + e.content = images_used.to_s + end - ds_usage.each { |ds_id, array| - images_used,size_used = array + ds_elem.xpath('SIZE_USED').each do |e| + next if e.text == size_used.to_s - log_error("#{resource} #{oid} quotas: Datastore #{ds_id}\tIMAGES_USED has 0 \tis\t#{images_used}") - log_error("#{resource} #{oid} quotas: Datastore #{ds_id}\tSIZE_USED has 0 \tis\t#{size_used}") + log_error("#{resource} #{oid} quotas: Datastore " \ + "#{ds_id}\tSIZE_USED has #{e.text} " \ + "\tis\t#{size_used}") + e.content = size_used.to_s + end + end - new_elem = ds_quota.add_child(doc.create_element("DATASTORE")) + ds_usage.each do |ds_id, array| + images_used, size_used = array - new_elem.add_child(doc.create_element("ID")).content = ds_id + log_error("#{resource} #{oid} quotas: Datastore " \ + "#{ds_id}\tIMAGES_USED has 0 \tis\t#{images_used}") + log_error("#{resource} #{oid} quotas: Datastore " \ + "#{ds_id}\tSIZE_USED has 0 \tis\t#{size_used}") - new_elem.add_child(doc.create_element("IMAGES")).content = "-1" - new_elem.add_child(doc.create_element("IMAGES_USED")).content = images_used.to_s + new_elem = ds_quota.add_child(doc.create_element('DATASTORE')) - new_elem.add_child(doc.create_element("SIZE")).content = "-1" - new_elem.add_child(doc.create_element("SIZE_USED")).content = size_used.to_s - } + new_elem.add_child(doc.create_element('ID')).content = ds_id + + images_el = doc.create_element('IMAGES_USED') + new_elem.add_child(doc.create_element('IMAGES')).content = '-1' + new_elem.add_child(images_el).content = images_used.to_s + + size_el = doc.create_element('SIZE_USED') + new_elem.add_child(doc.create_element('SIZE')).content = '-1' + new_elem.add_child(size_el).content = size_used.to_s + end end -end + # Calculate image quotas + # + # @param doc [Nokogiri::XML] xml document with all information + # @param query [String] database query + # @param resource [String] OpenNebula object + def calculate_image_quotas(doc, query, resource) + oid = doc.root.at_xpath('ID').text.to_i + + img_usage = {} + + @db.fetch(query) do |vm_row| + vmdoc = nokogiri_doc(vm_row[:body]) + + vmdoc.root.xpath('TEMPLATE/DISK/IMAGE_ID').each do |e| + img_usage[e.text] = 0 if img_usage[e.text].nil? + img_usage[e.text] += 1 + end + end + + img_quota = nil + doc.root.xpath('IMAGE_QUOTA').each {|e| img_quota = e } + + if img_quota.nil? + img_quota = doc.root.add_child(doc.create_element('IMAGE_QUOTA')) + end + + img_quota.xpath('IMAGE').each do |img_elem| + img_id = img_elem.at_xpath('ID').text + + rvms = img_usage.delete(img_id) + + rvms = 0 if rvms.nil? + + img_elem.xpath('RVMS_USED').each do |e| + next if e.text == rvms.to_s + + log_error("#{resource} #{oid} quotas: Image " \ + "#{img_id}\tRVMS has #{e.text} \tis\t#{rvms}") + e.content = rvms.to_s + end + end + + img_usage.each do |img_id, rvms| + log_error("#{resource} #{oid} quotas: Image " \ + "#{img_id}\tRVMS has 0 \tis\t#{rvms}") + + new_elem = img_quota.add_child(doc.create_element('IMAGE')) + + rvms_el = doc.create_element('RVMS_USED') + new_elem.add_child(doc.create_element('ID')).content = img_id + new_elem.add_child(doc.create_element('RVMS')).content = '-1' + new_elem.add_child(rvms_el).content = rvms.to_s + end + end + + # Calculate system quotas + # + # @param doc [Nokogiri::XML] xml document with all information + # @param query [String] database query + # @param resource [String] OpenNebula object + # @param vm_elem [Object] VM information + # + # @return [Object] datastore usage + def calculate_system_quotas(doc, query, resource, vm_elem) + oid = doc.root.at_xpath('ID').text.to_i + sys_used = 0 + datastore_usage = {} + + @db.fetch(query) do |vm_row| + vmdoc = nokogiri_doc(vm_row[:body]) + + vmdoc.root.xpath('TEMPLATE/DISK').each do |e| + type = '' + + e.xpath('TYPE').each {|t_elem| type = t_elem.text.upcase } + + size = 0 + + if !e.at_xpath('SIZE').nil? + size = e.at_xpath('SIZE').text.to_i + end + + if %w[SWAP FS].include? type + sys_used += size + + next + end + + next if e.at_xpath('CLONE').nil? + + clone = e.at_xpath('CLONE').text.casecmp('YES').zero? + + target = nil + + if clone && !e.at_xpath('CLONE_TARGET').nil? + target = e.at_xpath('CLONE_TARGET').text + elsif !clone && !e.at_xpath('LN_TARGET').nil? + target = e.at_xpath('LN_TARGET').text + end + + s_path = 'DISK_SNAPSHOT_TOTAL_SIZE' + + if !target.nil? && target == 'SYSTEM' + sys_used += size + + if !e.at_xpath(s_path).nil? + sys_used += e.at_xpath(s_path).text.to_i + end + elsif !target.nil? && target == 'SELF' + datastore_id = e.at_xpath('DATASTORE_ID').text + datastore_usage[datastore_id] ||= 0 + datastore_usage[datastore_id] += size + + if !e.at_xpath(s_path).nil? + s_size = e.at_xpath(s_path).text.to_i + datastore_usage[datastore_id] += s_size + end + end + end + end + + vm_elem.xpath('SYSTEM_DISK_SIZE_USED').each do |e| + next if e.text == sys_used.to_s + + log_error("#{resource} #{oid} quotas: SYSTEM_DISK_SIZE_USED " \ + "has #{e.text} \tis\t#{sys_used}") + e.content = sys_used.to_s + end + + datastore_usage + end + + # Calculate VM quotas + # + # @param doc [Nokogiri::XML] xml document with all information + # @param query [String] database query + # @param resource [String] OpenNebula object + # @param resources [Object] name of each quota resource + # + # @return [Object] VM information + def calculate_vm_quotas(doc, query, resource, resources) + oid = doc.root.at_xpath('ID').text.to_i + + cpu_used = 0 + mem_used = 0 + vms_used = 0 + + cpu = resources[:cpu] + mem = resources[:mem] + vms = resources[:vms] + + @db.fetch(query) do |vm_row| + vmdoc = nokogiri_doc(vm_row[:body]) + + vmdoc.root.xpath('TEMPLATE/CPU').each do |e| + # truncate to 2 decimals + cpu_used += (e.text.to_f * 100).to_i + end + + vmdoc.root.xpath('TEMPLATE/MEMORY').each do |e| + mem_used += e.text.to_i + end + + vms_used += 1 + end + + vm_elem = nil + doc.root.xpath('VM_QUOTA/VM').each {|e| vm_elem = e } + + if vm_elem.nil? + doc.root.xpath('VM_QUOTA').each {|e| e.remove } + + vm_quota = doc.root.add_child(doc.create_element('VM_QUOTA')) + vm_elem = vm_quota.add_child(doc.create_element('VM')) + + vm_elem.add_child(doc.create_element(cpu)).content = '-1' + vm_elem.add_child(doc.create_element("#{cpu}_USED")).content = '0' + + vm_elem.add_child(doc.create_element(mem)).content = '-1' + vm_elem.add_child(doc.create_element("#{mem}_USED")).content = '0' + + vm_elem.add_child(doc.create_element(vms)).content = '-1' + vm_elem.add_child(doc.create_element("#{vms}_USED")).content = '0' + end + + vm_elem.xpath("#{cpu}_USED").each do |e| + cpu_used = (cpu_used / 100.0) + + different = (e.text.to_f != cpu_used || + ![format('%.2f', cpu_used), + format('%.1f', cpu_used), + format('%.0f', cpu_used)].include?(e.text)) + + cpu_used_str = format('%.2f', cpu_used) + + next unless different + + log_error("#{resource} #{oid} quotas: #{cpu}_USED has " \ + "#{e.text} \tis\t#{cpu_used_str}") + e.content = cpu_used_str + end + + vm_elem.xpath("#{mem}_USED").each do |e| + next if e.text == mem_used.to_s + + log_error("#{resource} #{oid} quotas: #{mem}_USED has " \ + "#{e.text} \tis\t#{mem_used}") + e.content = mem_used.to_s + end + + vm_elem.xpath("#{vms}_USED").each do |e| + next if e.text == vms_used.to_s + + log_error("#{resource} #{oid} quotas: #{vms}_USED has " \ + "#{e.text} \tis\t#{vms_used}") + e.content = vms_used.to_s + end + + vm_elem + end + + # Calculate vnet quotas + # + # @param doc [Nokogiri::XML] xml document with all information + # @param query [String] database query + # @param resource [String] OpenNebula object + def calculate_vnet_quotas(doc, queries, resource) + oid = doc.root.at_xpath('ID').text.to_i + + vnet_usage = {} + + @db.fetch(queries[0]) do |vm_row| + vmdoc = nokogiri_doc(vm_row[:body]) + + vmdoc.root.xpath('TEMPLATE/NIC/NETWORK_ID').each do |e| + vnet_usage[e.text] = 0 if vnet_usage[e.text].nil? + vnet_usage[e.text] += 1 + end + end + + @db.fetch(queries[1]) do |vrouter_row| + vrouter_doc = Nokogiri::XML(vrouter_row[:body], + nil, + NOKOGIRI_ENCODING) do |c| + c.default_xml.noblanks + end + + vrouter_doc.root.xpath('TEMPLATE/NIC').each do |nic| + net_id = nil + + nic.xpath('NETWORK_ID').each do |nid| + net_id = nid.text + end + + floating = false + + nic.xpath('FLOATING_IP').each do |floating_e| + floating = floating_e.text.casecmp('YES').zero? + end + + if !net_id.nil? && floating + vnet_usage[net_id] = 0 if vnet_usage[net_id].nil? + vnet_usage[net_id] += 1 + end + end + end + + net_quota = nil + doc.root.xpath('NETWORK_QUOTA').each {|e| net_quota = e } + + if net_quota.nil? + net_quota = doc.root.add_child(doc.create_element('NETWORK_QUOTA')) + end + + net_quota.xpath('NETWORK').each do |net_elem| + vnet_id = net_elem.at_xpath('ID').text + + leases_used = vnet_usage.delete(vnet_id) + + leases_used = 0 if leases_used.nil? + + net_elem.xpath('LEASES_USED').each do |e| + next if e.text == leases_used.to_s + + log_error("#{resource} #{oid} quotas: VNet " \ + "#{vnet_id}\tLEASES_USED has #{e.text} " \ + "\tis\t#{leases_used}") + e.content = leases_used.to_s + end + end + + vnet_usage.each do |vnet_id, leases_used| + log_error("#{resource} #{oid} quotas: VNet " \ + "#{vnet_id}\tLEASES_USED has 0 \tis\t#{leases_used}") + + new_elem = net_quota.add_child(doc.create_element('NETWORK')) + + leases_el = doc.create_element('LEASES_USED') + new_elem.add_child(doc.create_element('ID')).content = vnet_id + new_elem.add_child(doc.create_element('LEASES')).content = '-1' + new_elem.add_child(leases_el).content = leases_used.to_s + end + end + +end