From 1cb92fac5ce4ba049dd0278e73c2ea93b3efb4d9 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Sun, 3 Jul 2011 15:18:42 +0200
Subject: [PATCH] htools: add parseUnit function

This is similar, but not identical, to Utils.ParseUnit. The biggest
difference is that we don't round up/down; as we only use integral
types, the result will always be rounded down.

Moreover, since (real-world) disk sizes come in SI units, the function
differentiates between SI and binary prefixes, using lower-case for
binary and upper-case for SI (similar to lvm usage). This distinction
should be ported to the Python code.

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Guido Trotter <ultrotter@google.com>
---
 htools/Ganeti/HTools/QC.hs    | 14 ++++++++++++++
 htools/Ganeti/HTools/Types.hs |  2 +-
 htools/Ganeti/HTools/Utils.hs | 29 +++++++++++++++++++++++++++++
 3 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs
index a48714d0b..40bf43797 100644
--- a/htools/Ganeti/HTools/QC.hs
+++ b/htools/Ganeti/HTools/QC.hs
@@ -334,6 +334,19 @@ prop_Utils_select_undefv lst1 (NonEmpty lst2) =
         tlist = map (\e -> (True, e)) lst2
         cndlist = flist ++ tlist ++ [undefined]
 
+prop_Utils_parseUnit (NonNegative n) =
+    Utils.parseUnit (show n) == Types.Ok n &&
+    Utils.parseUnit (show n ++ "m") == Types.Ok n &&
+    (case Utils.parseUnit (show n ++ "M") of
+      Types.Ok m -> if n > 0
+                    then m < n  -- for positive values, X MB is less than X MiB
+                    else m == 0 -- but for 0, 0 MB == 0 MiB
+      Types.Bad _ -> False) &&
+    Utils.parseUnit (show n ++ "g") == Types.Ok (n*1024) &&
+    Utils.parseUnit (show n ++ "t") == Types.Ok (n*1048576) &&
+    Types.isBad (Utils.parseUnit (show n ++ "x")::Types.Result Int)
+    where _types = (n::Int)
+
 -- | Test list for the Utils module.
 testUtils =
   [ run prop_Utils_commaJoinSplit
@@ -343,6 +356,7 @@ testUtils =
   , run prop_Utils_select
   , run prop_Utils_select_undefd
   , run prop_Utils_select_undefv
+  , run prop_Utils_parseUnit
   ]
 
 -- ** PeerMap tests
diff --git a/htools/Ganeti/HTools/Types.hs b/htools/Ganeti/HTools/Types.hs
index efa2f0a0a..24ea25bf0 100644
--- a/htools/Ganeti/HTools/Types.hs
+++ b/htools/Ganeti/HTools/Types.hs
@@ -264,7 +264,7 @@ mini-library here
 data Result a
     = Bad String
     | Ok a
-    deriving (Show, Read)
+    deriving (Show, Read, Eq)
 
 instance Monad Result where
     (>>=) (Bad x) _ = Bad x
diff --git a/htools/Ganeti/HTools/Utils.hs b/htools/Ganeti/HTools/Utils.hs
index cff474b55..d4e8024da 100644
--- a/htools/Ganeti/HTools/Utils.hs
+++ b/htools/Ganeti/HTools/Utils.hs
@@ -46,9 +46,11 @@ module Ganeti.HTools.Utils
     , formatTable
     , annotateResult
     , defaultGroupID
+    , parseUnit
     ) where
 
 import Control.Monad (liftM)
+import Data.Char (toUpper)
 import Data.List
 import Data.Maybe (fromMaybe)
 import qualified Text.JSON as J
@@ -252,3 +254,30 @@ formatTable vals numpos =
 -- | Default group UUID (just a string, not a real UUID).
 defaultGroupID :: GroupID
 defaultGroupID = "00000000-0000-0000-0000-000000000000"
+
+-- | Tries to extract number and scale from the given string.
+--
+-- Input must be in the format NUMBER+ SPACE* [UNIT]. If no unit is
+-- specified, it defaults to MiB. Return value is always an integral
+-- value in MiB.
+parseUnit :: (Monad m, Integral a, Read a) => String -> m a
+parseUnit str =
+    -- TODO: enhance this by splitting the unit parsing code out and
+    -- accepting floating-point numbers
+    case reads str of
+      [(v, suffix)] ->
+          let unit = dropWhile (== ' ') suffix
+              upper = map toUpper unit
+              siConvert x = x * 1000000 `div` 1048576
+          in case () of
+               _ | null unit -> return v
+                 | unit == "m" || upper == "MIB" -> return v
+                 | unit == "M" || upper == "MB"  -> return $ siConvert v
+                 | unit == "g" || upper == "GIB" -> return $ v * 1024
+                 | unit == "G" || upper == "GB"  -> return $ siConvert
+                                                    (v * 1000)
+                 | unit == "t" || upper == "TIB" -> return $ v * 1048576
+                 | unit == "T" || upper == "TB"  -> return $
+                                                    siConvert (v * 1000000)
+                 | otherwise -> fail $ "Unknown unit '" ++ unit ++ "'"
+      _ -> fail $ "Can't parse string '" ++ str ++ "'"
-- 
GitLab