From 6120d8aba0ca8a818ddccf24deca74adfc4a43c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Carlos=20Mart=C3=ADn?= <cmartin@opennebula.org>
Date: Thu, 12 Feb 2015 11:33:19 +0100
Subject: [PATCH] Feature #3056: Fix vnet leases in fsck

---
 src/onedb/fsck.rb       | 433 ++++++++++++++++++++++++++++++++++++++--
 src/vnm/AddressRange.cc |   6 +-
 2 files changed, 422 insertions(+), 17 deletions(-)

diff --git a/src/onedb/fsck.rb b/src/onedb/fsck.rb
index 0b445f567b..4d150a7963 100644
--- a/src/onedb/fsck.rb
+++ b/src/onedb/fsck.rb
@@ -51,6 +51,10 @@ EOT
 
     IMAGE_STATES=%w{INIT READY USED DISABLED LOCKED ERROR CLONE DELETE USED_PERS}
 
+    VM_BIN  = 0x0000001000000000
+    NET_BIN = 0x0000004000000000
+    HOLD    = 0xFFFFFFFF
+
     def fsck
 
         ########################################################################
@@ -756,11 +760,16 @@ EOT
 
         # Init vnet counters
         @db.fetch("SELECT oid,body FROM network_pool") do |row|
-            doc = Document.new(row[:body])
+            doc = Nokogiri::XML(row[:body]){|c| c.default_xml.noblanks}
+
+            ar_leases = {}
+
+            doc.root.xpath("AR_POOL/AR/AR_ID").each do |ar_id|
+                ar_leases[ar_id.text.to_i] = {}
+            end
 
             counters[:vnet][row[:oid]] = {
-                :type           => doc.root.get_text('TYPE').to_s.to_i,
-                :ar_leases      => {}
+                :ar_leases      => ar_leases
             }
         end
 
@@ -788,9 +797,9 @@ EOT
             end
 
             # VNets used by this VM
-            vm_doc.root.xpath("TEMPLATE/NIC").each do |e|
+            vm_doc.root.xpath("TEMPLATE/NIC").each do |nic|
                 net_id = nil
-                e.xpath("NETWORK_ID").each do |nid|
+                nic.xpath("NETWORK_ID").each do |nid|
                     net_id = nid.text.to_i
                 end
 
@@ -799,20 +808,25 @@ EOT
                         log_error("VM #{row[:oid]} is using VNet #{net_id}, "<<
                             "but it does not exist")
                     else
-=begin
-                        ar_id_e = e.at_xpath('AR_ID')
+                        ar_id_e = nic.at_xpath('AR_ID')
                         ar_id = ar_id_e.nil? ? -1 : ar_id_e.text.to_i
 
                         if counters[:vnet][net_id][:ar_leases][ar_id].nil?
-                            counters[:vnet][net_id][:ar_leases][ar_id] = []
+                            log_error("VM #{row[:oid]} is using VNet #{net_id}, AR #{ar_id}, "<<
+                                "but the AR does not exist")
                         end
 
-                        counters[:vnet][net_id][:ar_leases][ar_id] <<
-                            [
-                                e.at_xpath('MAC').text,                 # MAC
-                                vm_doc.root.at_xpath('ID').text.to_i    # VID
-                            ]
-=end
+                        mac = nic.at_xpath("MAC").nil? ? nil : nic.at_xpath("MAC").text
+
+                        counters[:vnet][net_id][:ar_leases][ar_id][mac_s_to_i(mac)] = {
+                            :ip         => nic.at_xpath("IP").nil? ? nil : nic.at_xpath("IP").text,
+                            :ip6_global => nic.at_xpath("IP6_GLOBAL").nil? ? nil : nic.at_xpath("IP6_GLOBAL").text,
+                            :ip6_link   => nic.at_xpath("IP6_LINK").nil? ? nil : nic.at_xpath("IP6_LINK").text,
+                            :ip6_ula    => nic.at_xpath("IP6_ULA").nil? ? nil : nic.at_xpath("IP6_ULA").text,
+                            :mac        => mac,
+                            :vm         => row[:oid],
+                            :vnet       => nil
+                        }
                     end
                 end
             end
@@ -1103,6 +1117,360 @@ EOT
 
         log_time()
 
+        ########################################################################
+        # VNet
+        #
+        # LEASES
+        ########################################################################
+
+        @db.fetch("SELECT oid,body,pid FROM network_pool WHERE pid<>-1") do |row|
+            doc = Nokogiri::XML(row[:body]){|c| c.default_xml.noblanks}
+
+            parent_vnet = doc.root.at_xpath("PARENT_NETWORK_ID").text.to_i
+
+            if (row[:pid] != parent_vnet)
+                # TODO
+            end
+
+            doc.root.xpath("AR_POOL/AR").each do |ar|
+                parent_ar_e = ar.at_xpath("PARENT_NETWORK_AR_ID")
+                if !(parent_ar_e.nil? || parent_ar_e.text == "")
+
+                    parent_ar = parent_ar_e.text.to_i
+
+                    if counters[:vnet][parent_vnet][:ar_leases][parent_ar].nil?
+                        log_error(
+                            "VNet #{row[:oid]} is using parent "<<
+                            "VNet #{parent_vnet}, AR #{parent_ar}, "<<
+                            "but the AR does not exist")
+                    end
+
+                    # MAC
+                    first_mac = mac_s_to_i(ar.at_xpath("MAC").text)
+
+                    # IP
+                    first_ip = nil
+                    if (!ar.at_xpath("IP").nil?)
+                        first_ip = IPAddr.new(ar.at_xpath("IP").text, Socket::AF_INET)
+                    end
+
+                    # IP6
+                    global_prefix = nil
+                    if !ar.at_xpath("GLOBAL_PREFIX").nil?
+                        global_prefix = ip6_prefix_s_to_i(
+                                    ar.at_xpath("GLOBAL_PREFIX").text)
+                    end
+
+                    ula_prefix = nil
+                    if !ar.at_xpath("ULA_PREFIX").nil?
+                        ula_prefix = ip6_prefix_s_to_i(
+                                    ar.at_xpath("ULA_PREFIX").text)
+                    end
+
+                    link_prefix = nil
+                    type = ar.at_xpath("TYPE").text
+                    if ( type == "IP6" || type == "IP4_6" )
+                        link_prefix = 0xfe80000000000000
+                    end
+
+                    # Parent vnet has a lease for each address of this reservation
+                    ar.at_xpath("SIZE").text.to_i.times do |index|
+
+                        lease = {
+                            :ip         => nil,
+                            :ip6_global => nil,
+                            :ip6_link   => nil,
+                            :ip6_ula    => nil,
+                            :mac        => nil,
+                            :vm         => nil,
+                            :vnet       => row[:oid]
+                        }
+
+                        #MAC
+                        mac = (first_mac & 0xFFFF00000000) +
+                              (((first_mac & 0xFFFFFFFF) + index) % 0x100000000)
+                        lease[:mac] = mac_i_to_s(mac)
+
+                        # IP
+                        if (!first_ip.nil?)
+                            lease[:ip] = IPAddr.new(first_ip.to_i + index,
+                                                    Socket::AF_INET).to_s
+                        end
+
+                        # IP6
+                        ip6_suffix = mac_to_ip6_suffix(mac)
+
+                        if (!global_prefix.nil?)
+                            lease[:ip6_global] = IPAddr.new(
+                                (global_prefix << 64) | ip6_suffix,
+                                Socket::AF_INET6 ).to_s
+                        end
+
+                        if (!ula_prefix.nil?)
+                            lease[:ip6_ula] = IPAddr.new(
+                                (ula_prefix << 64) | ip6_suffix,
+                                Socket::AF_INET6 ).to_s
+                        end
+
+                        if (!link_prefix.nil?)
+                            lease[:ip6_link] = IPAddr.new(
+                                (link_prefix << 64) | ip6_suffix,
+                                Socket::AF_INET6 ).to_s
+                        end
+
+                        counters[:vnet][parent_vnet][
+                            :ar_leases][parent_ar][mac] = lease
+                    end
+                end
+            end
+        end
+
+
+        # Create a new empty table where we will store the new calculated values
+        @db.run "CREATE TABLE network_pool_new (oid INTEGER PRIMARY KEY, name VARCHAR(128), body MEDIUMTEXT, uid INTEGER, gid INTEGER, owner_u INTEGER, group_u INTEGER, other_u INTEGER, cid INTEGER, pid INTEGER, UNIQUE(name,uid));"
+
+        @db.transaction do
+        @db[:network_pool].each do |row|
+            doc = Nokogiri::XML(row[:body]){|c| c.default_xml.noblanks}
+            oid = row[:oid]
+
+            used_leases = doc.root.at_xpath("USED_LEASES").text.to_i
+            new_used_leases = 0
+
+            counters[:vnet][row[:oid]][:ar_leases].each do |ar_id, counter_ar|
+                net_ar = doc.root.at_xpath("AR_POOL/AR[AR_ID=#{ar_id}]")
+
+                if (net_ar.nil?)
+                    # TODO shouldn't happen?
+                end
+
+                # MAC
+                first_mac = mac_s_to_i(net_ar.at_xpath("MAC").text)
+
+                # IP
+                first_ip = nil
+                if !net_ar.at_xpath("IP").nil?
+                    first_ip = IPAddr.new(net_ar.at_xpath("IP").text, Socket::AF_INET)
+                end
+
+                # IP6
+                global_prefix = nil
+                if !net_ar.at_xpath("GLOBAL_PREFIX").nil?
+                    global_prefix = ip6_prefix_s_to_i(
+                                net_ar.at_xpath("GLOBAL_PREFIX").text)
+                end
+
+                ula_prefix = nil
+                if !net_ar.at_xpath("ULA_PREFIX").nil?
+                    ula_prefix = ip6_prefix_s_to_i(
+                                net_ar.at_xpath("ULA_PREFIX").text)
+                end
+
+                link_prefix = nil
+                type = net_ar.at_xpath("TYPE").text
+                if ( type == "IP6" || type == "IP4_6" )
+                    link_prefix = 0xfe80000000000000
+                end
+
+                # Allocated leases
+                allocated_e = net_ar.at_xpath("ALLOCATED")
+
+                allocated = allocated_e.nil? ? "" : allocated_e.text
+
+                leases = allocated.scan(/(\d+) (\d+)/)
+
+                new_leases = []
+
+                leases.each do |lease_str|
+                    index = lease_str[0].to_i
+                    binary_magic = lease_str[1].to_i
+
+                    lease = {
+                        :ip         => nil,
+                        :ip6_global => nil,
+                        :ip6_link   => nil,
+                        :ip6_ula    => nil,
+                        :mac        => nil,
+                        :vm         => nil,
+                        :vnet       => nil
+                    }
+
+                    # MAC
+                    mac = (first_mac & 0xFFFF00000000) +
+                          (((first_mac & 0xFFFFFFFF) + index) % 0x100000000)
+
+                    lease[:mac] = mac_i_to_s(mac)
+
+                    # IP
+                    if (!first_ip.nil?)
+                        lease[:ip] = IPAddr.new(first_ip.to_i + index,
+                                                Socket::AF_INET).to_s
+                    end
+
+                    # IP6
+                    ip6_suffix = mac_to_ip6_suffix(mac)
+
+                    if (!global_prefix.nil?)
+                        lease[:ip6_global] = IPAddr.new(
+                            (global_prefix << 64) | ip6_suffix,
+                            Socket::AF_INET6 ).to_s
+                    end
+
+                    if (!ula_prefix.nil?)
+                        lease[:ip6_ula] = IPAddr.new(
+                            (ula_prefix << 64) | ip6_suffix,
+                            Socket::AF_INET6 ).to_s
+                    end
+
+                    if (!link_prefix.nil?)
+                        lease[:ip6_link] = IPAddr.new(
+                            (link_prefix << 64) | ip6_suffix,
+                            Socket::AF_INET6 ).to_s
+                    end
+
+                    # OID
+                    lease_oid = binary_magic & 0x00000000FFFFFFFF
+                    lease_obj = ""
+
+                    if (binary_magic & VM_BIN != 0)
+                        lease[:vm] = lease_oid
+                        lease_obj = "VM"
+                    else # binary_magic & NET_BIN != 0
+                        lease[:vnet] = lease_oid
+                        lease_obj = "VNet"
+                    end
+
+                    counter_lease = counter_ar[mac]
+                    counter_ar.delete(mac)
+
+                    if counter_lease.nil?
+                        if(lease[:vm] != HOLD)
+                            log_error(
+                            "VNet #{oid} AR #{ar_id} has leased #{lease_to_s(lease)} "<<
+                            "to #{lease_obj} #{lease_oid}, but it is actually free")
+                        else
+                            new_leases << lease_str
+                        end
+                    else
+                        if counter_lease != lease
+
+                            # Things that can be fixed
+                            if (counter_lease[:vm] != lease[:vm] ||
+                                counter_lease[:vnet] != lease[:vnet])
+
+                                new_lease_obj = ""
+                                new_lease_oid = 0
+                                new_binary_magic = 0
+
+                                if !counter_lease[:vm].nil?
+                                    new_lease_obj = "VM"
+                                    new_lease_oid = counter_lease[:vm].to_i
+
+                                    new_binary_magic = (VM_BIN |
+                                        (new_lease_oid & 0xFFFFFFFF))
+                                else
+                                    new_lease_obj = "VNet"
+                                    new_lease_oid = counter_lease[:vnet].to_i
+
+                                    new_binary_magic = (NET_BIN |
+                                        (new_lease_oid & 0xFFFFFFFF))
+                                end
+
+                                if (lease[:vm] == HOLD)
+                                    log_error(
+                                        "VNet #{oid} AR #{ar_id} has lease "<<
+                                        "#{lease_to_s(lease)} on hold, but it is "<<
+                                        "actually used by "<<
+                                        "#{new_lease_obj} #{new_lease_oid}")
+                                else
+                                    log_error(
+                                        "VNet #{oid} AR #{ar_id} has leased #{lease_to_s(lease)} "<<
+                                        "to #{lease_obj} #{lease_oid}, but it is "<<
+                                        "actually used by "<<
+                                        "#{new_lease_obj} #{new_lease_oid}")
+                                end
+
+                                lease_str[1] = new_binary_magic.to_s
+                            end
+
+                            # Things that can't be fixed
+
+                            [:ip, :ip6_global, :ip6_link, :ip6_ula].each do |key|
+                                if (counter_lease[key] != lease[key])
+                                    log_error(
+                                    "VNet #{oid} AR #{ar_id} has a wrong "<<
+                                    "lease for "<<
+                                    "#{lease_obj} #{lease_oid}. #{key.to_s.upcase} "<<
+                                    "does not match: "<<
+                                    "#{counter_lease[key]} != #{lease[key]}. "<<
+                                    "This can't be fixed")
+                                end
+                            end
+                        end
+
+                        new_leases << lease_str
+                    end
+                end
+
+                counter_ar.each do |mac, counter_lease|
+                    index = ((mac & 0xFFFFFFFF) - (first_mac & 0xFFFFFFFF) ) % 0x100000000
+
+                    new_lease_obj = ""
+                    new_lease_oid = 0
+                    new_binary_magic = 0
+
+                    if !counter_lease[:vm].nil?
+                        new_lease_obj = "VM"
+                        new_lease_oid = counter_lease[:vm].to_i
+
+                        new_binary_magic = (VM_BIN |
+                            (new_lease_oid & 0xFFFFFFFF))
+                    else
+                        new_lease_obj = "VNet"
+                        new_lease_oid = counter_lease[:vnet].to_i
+
+                        new_binary_magic = (NET_BIN |
+                            (new_lease_oid & 0xFFFFFFFF))
+                    end
+
+                    log_error("VNet #{oid} AR #{ar_id} does not have a lease "<<
+                        "for #{mac_i_to_s(mac)}, but it is in use by "<<
+                        "#{new_lease_obj} #{new_lease_oid}")
+
+                    new_leases << [index.to_s, new_binary_magic.to_s]
+                end
+
+                new_used_leases += new_leases.size
+
+                if new_leases.size > 0
+                    allocated_e.content = " #{new_leases.join(" ")}"
+                else
+                    allocated_e.remove if !allocated_e.nil?
+                end
+            end
+
+            if (new_used_leases != used_leases)
+                log_error("VNet #{oid} has #{used_leases} used leases, "<<
+                "but it is actually #{new_used_leases}")
+
+                doc.root.at_xpath("USED_LEASES").content =
+                                                new_used_leases.to_s
+            end
+
+            row[:body] = doc.root.to_s
+
+            # commit
+            @db[:network_pool_new].insert(row)
+        end
+        end
+
+        # Rename table
+        @db.run("DROP TABLE network_pool")
+        @db.run("ALTER TABLE network_pool_new RENAME TO network_pool")
+
+        log_time()
+
+
         ########################################################################
         # Users
         #
@@ -1477,4 +1845,41 @@ EOT
             new_elem.add_child(doc.create_element("SIZE_USED")).content = size_used.to_s
         }
     end
+
+    def mac_s_to_i(mac)
+        return mac.split(":").map {|e|
+            e.to_i(16).to_s(16).rjust(2,"0")}.join("").to_i(16)
+    end
+
+    def mac_i_to_s(mac)
+        mac_s = mac.to_s(16).rjust(12, "0")
+        return "#{mac_s[0..1]}:#{mac_s[2..3]}:#{mac_s[4..5]}:"<<
+               "#{mac_s[6..7]}:#{mac_s[8..9]}:#{mac_s[10..11]}"
+    end
+
+    def ip6_prefix_s_to_i(prefix)
+        return prefix.split(":", 4).map {|e|
+            e.to_i(16).to_s(16).rjust(4, "0")}.join("").to_i(16)
+    end
+
+    # Copied from AddressRange::set_ip6 in AddressRange.cc
+    def mac_to_ip6_suffix(mac_i)
+        mac = [
+            mac_i & 0x00000000FFFFFFFF,
+            (mac_i & 0xFFFFFFFF00000000) >> 32
+        ]
+
+        mlow = mac[0]
+        eui64 = [
+            4261412864 + (mlow & 0x00FFFFFF),
+            ((mac[1]+512)<<16) + ((mlow & 0xFF000000)>>16) + 0x000000FF
+        ]
+
+        return (eui64[1] << 32) + eui64[0]
+    end
+
+    def lease_to_s(lease)
+        return lease[:ip].nil? ? lease[:mac].to_s : lease[:ip].to_s
+    end
+
 end
diff --git a/src/vnm/AddressRange.cc b/src/vnm/AddressRange.cc
index 2f658fea80..337c6a1e69 100644
--- a/src/vnm/AddressRange.cc
+++ b/src/vnm/AddressRange.cc
@@ -1072,12 +1072,12 @@ int AddressRange::free_addr(PoolObjectSQL::ObjectType ot, int obid,
 
     unsigned int index = mac_i[0] - mac[0];
 
-    if ((0 <= index) && (index < size))
+    if ( index < 0)
     {
-        return free_addr(ot, obid, index);
+        return -1;
     }
 
-    return -1;
+    return free_addr(ot, obid, index);
 }
 
 /* -------------------------------------------------------------------------- */