commit 11e9058807fb648e1b1017c43916a73d0251bb06
Author: Dimitris Aragiorgis <dimara@grnet.gr>
Date:   Wed Oct 2 22:36:54 2013 +0300

    (b64) Use base64 encoding in networks' bitarrays
    
    This is needed for GRNET'S production environment.
    
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>

diff --git a/lib/network.py b/lib/network.py
index 8059e31..1ba80fa 100644
--- a/lib/network.py
+++ b/lib/network.py
@@ -26,6 +26,8 @@
 import ipaddr
 
 from bitarray import bitarray
+from base64 import b64encode
+from base64 import b64decode
 
 from ganeti import errors
 
@@ -92,14 +94,18 @@ class AddressPool(object):
       self.gateway6 = ipaddr.IPv6Address(self.net.gateway6)
 
     if self.net.reservations:
-      self.reservations = bitarray(self.net.reservations)
+      self.reservations = bitarray()
+      # pylint: disable=E1103
+      self.reservations.frombytes(b64decode(self.net.reservations))
     else:
       self.reservations = bitarray(self.network.numhosts)
       # pylint: disable=E1103
       self.reservations.setall(False)
 
     if self.net.ext_reservations:
-      self.ext_reservations = bitarray(self.net.ext_reservations)
+      self.ext_reservations = bitarray()
+      # pylint: disable=E1103
+      self.ext_reservations.frombytes(b64decode(self.net.ext_reservations))
     else:
       self.ext_reservations = bitarray(self.network.numhosts)
       # pylint: disable=E1103
@@ -129,8 +135,8 @@ class AddressPool(object):
 
     """
     # pylint: disable=E1103
-    self.net.ext_reservations = self.ext_reservations.to01()
-    self.net.reservations = self.reservations.to01()
+    self.net.ext_reservations = b64encode(self.ext_reservations.tobytes())
+    self.net.reservations = b64encode(self.reservations.tobytes())
 
   def _Mark(self, address, value=True, external=False):
     idx = self._GetAddrIndex(address)
diff --git a/src/Ganeti/Network.hs b/src/Ganeti/Network.hs
index 5106213..1d89508 100644
--- a/src/Ganeti/Network.hs
+++ b/src/Ganeti/Network.hs
@@ -41,6 +41,7 @@ module Ganeti.Network
 import qualified Data.Vector.Unboxed as V
 
 import Ganeti.Objects
+import Ganeti.Utils (b64StringToBitString)
 
 -- | An address pool, holding a network plus internal and external
 -- reservations.
@@ -75,7 +76,7 @@ sameLength _ _ = False
 -- | Converts a maybe bit string to a bit vector. Returns an empty bit vector on
 -- nothing.
 maybeStr2BitVec :: Maybe String -> V.Vector Bool
-maybeStr2BitVec (Just s) = bitStringToBitVector s
+maybeStr2BitVec (Just s) = bitStringToBitVector $ b64StringToBitString s
 maybeStr2BitVec Nothing = V.fromList ([]::[Bool])
 
 -- | Converts a string to a bit vector. The character '0' is interpreted
diff --git a/src/Ganeti/Query/Network.hs b/src/Ganeti/Query/Network.hs
index b7487f4..2f48f02 100644
--- a/src/Ganeti/Query/Network.hs
+++ b/src/Ganeti/Query/Network.hs
@@ -46,6 +46,7 @@ import Ganeti.Query.Language
 import Ganeti.Query.Common
 import Ganeti.Query.Types
 import Ganeti.Types
+import Ganeti.Utils (b64StringToBitString)
 
 -- | There is no actual runtime.
 data Runtime = Runtime
@@ -175,7 +176,7 @@ getReservations (Ip4Network net _) =
 getExtReservationsString :: Network -> ResultEntry
 getExtReservationsString net =
   let addrs = getReservations (networkNetwork net)
-              (fromMaybe "" $ networkExtReservations net)
+              (b64StringToBitString $ fromMaybe "" $ networkExtReservations net)
   in rsNormal . intercalate ", " $ map show addrs
 
 -- | Dummy function for collecting live data (which networks don't have).
diff --git a/src/Ganeti/Utils.hs b/src/Ganeti/Utils.hs
index 026db21..c937f9f 100644
--- a/src/Ganeti/Utils.hs
+++ b/src/Ganeti/Utils.hs
@@ -58,6 +58,8 @@ module Ganeti.Utils
   , splitEithers
   , recombineEithers
   , setOwnerAndGroupFromNames
+  , b64StringToBitString
+  , bitStringToB64String
   ) where
 
 import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
@@ -77,6 +79,13 @@ import System.Exit
 import System.Posix.Files
 import System.Time
 
+import qualified Data.ByteString as BS
+import Data.ByteString.Base64 (decodeLenient, encode)
+import qualified Data.ByteString.Char8 as BSC
+import Data.Word (Word8)
+import Data.Char (intToDigit, digitToInt)
+import Numeric (showIntAtBase, readInt)
+
 -- * Debug functions
 
 -- | To be used only for debugging, breaks referential integrity.
@@ -458,3 +467,34 @@ setOwnerAndGroupFromNames filename daemon dGroup = do
   let uid = fst ents M.! daemon
   let gid = snd ents M.! dGroup
   setOwnerAndGroup filename uid gid
+
+type BitString = String
+
+-- | Base 64 encoded String to BitString
+wordsToBitString :: [Word8] -> BitString
+wordsToBitString =
+    concatMap (padBits . wordToBits)
+  where
+    wordToBits = flip (showIntAtBase 2 intToDigit) ""
+    padBits bs = replicate (8 - length bs) '0' ++ bs
+
+decodeB64String :: String -> [Word8]
+decodeB64String = BS.unpack . decodeLenient . BSC.pack
+
+b64StringToBitString :: String -> BitString
+b64StringToBitString = wordsToBitString . decodeB64String
+
+-- | A BitString to Base 64 encoded String
+bitStringToWords :: BitString -> [Word8]
+bitStringToWords [] = []
+bitStringToWords bs =
+    bitStringToWord c : bitStringToWords rest
+  where
+    bitStringToWord = fst . head . readInt 2 (const True) digitToInt
+    (c, rest) = splitAt 8 bs
+
+encodeB64String :: [Word8] -> String
+encodeB64String = BSC.unpack . encode . BS.pack
+
+bitStringToB64String :: BitString -> String
+bitStringToB64String  = encodeB64String . bitStringToWords
diff --git a/test/hs/Test/Ganeti/Objects.hs b/test/hs/Test/Ganeti/Objects.hs
index 0598a39..49de4c1 100644
--- a/test/hs/Test/Ganeti/Objects.hs
+++ b/test/hs/Test/Ganeti/Objects.hs
@@ -62,6 +62,7 @@ import Ganeti.Network
 import Ganeti.Objects as Objects
 import Ganeti.JSON
 import Ganeti.Types
+import Ganeti.Utils (bitStringToB64String)
 
 -- * Arbitrary instances
 
@@ -238,15 +239,17 @@ instance Arbitrary Network where
 genValidNetwork :: Gen Objects.Network
 genValidNetwork = do
   -- generate netmask for the IPv4 network
-  netmask <- fromIntegral <$> choose (24::Int, 30)
+  netmask <- fromIntegral <$> choose (24::Int, 29)
   name <- genName >>= mkNonEmpty
   mac_prefix <- genMaybe genName
   net <- arbitrary
   net6 <- genMaybe genIp6Net
   gateway <- genMaybe arbitrary
   gateway6 <- genMaybe genIp6Addr
-  res <- liftM Just (genBitString $ netmask2NumHosts netmask)
-  ext_res <- liftM Just (genBitString $ netmask2NumHosts netmask)
+  res <- liftM (Just . bitStringToB64String)
+         (genBitString $ netmask2NumHosts netmask)
+  ext_res <- liftM (Just . bitStringToB64String)
+             (genBitString $ netmask2NumHosts netmask)
   uuid <- arbitrary
   ctime <- arbitrary
   mtime <- arbitrary
diff --git a/tools/cfgupgrade b/tools/cfgupgrade
index 0d12b3e..d471f35 100755
--- a/tools/cfgupgrade
+++ b/tools/cfgupgrade
@@ -34,6 +34,8 @@ import optparse
 import logging
 import time
 from cStringIO import StringIO
+from bitarray import bitarray
+from base64 import b64encode, b64decode
 
 from ganeti import constants
 from ganeti import serializer
@@ -126,10 +128,29 @@ def UpgradeIPolicy(ipolicy, default_ipolicy, isgroup):
   _FillIPolicySpecs(default_ipolicy, ipolicy)
 
 
+# pylint: disable=E1101
 def UpgradeNetworks(config_data):
-  networks = config_data.get("networks", None)
+  networks = config_data.get("networks", {})
   if not networks:
     config_data["networks"] = {}
+  for nobj in networks.values():
+    for key in ("reservations", "ext_reservations"):
+      r = nobj[key]
+      if options.tob64:
+        try:
+          b = bitarray(r)
+          nobj[key] = b64encode(b.tobytes())
+        except ValueError:
+          print("No 01 network found! Probably already in base64.")
+      if options.to01:
+        try:
+          b = bitarray(r)
+          print("01 network found! Do nothing.")
+        except ValueError:
+          b = bitarray()
+          b.frombytes(b64decode(r))
+          nobj[key] = b.to01()
+      print("%s: %s -> %s" % (nobj["name"], r, nobj[key]))
 
 
 def UpgradeCluster(config_data):
@@ -395,6 +416,12 @@ def main():
   parser.add_option("--downgrade",
                     help="Downgrade to the previous stable version",
                     action="store_true", dest="downgrade", default=False)
+  parser.add_option("--tob64",
+                    help="Change to base64 encoded networks",
+                    action="store_true", dest="tob64", default=False)
+  parser.add_option("--to01",
+                    help="Change to non encoded networks (01 bitarrays)",
+                    action="store_true", dest="to01", default=False)
   (options, args) = parser.parse_args()
 
   # We need to keep filenames locally because they might be renamed between
