Implement functionality to import MonD data from a mock file to
HTools (mainly for testing purposes) with the --mond-data
option.

Signed-off-by: Spyros Trigazis <[email protected]>
---
 src/Ganeti/HTools/CLI.hs          |   11 +++++++
 src/Ganeti/HTools/ExtLoader.hs    |   65 ++++++++++++++++++++++++++++++-------
 src/Ganeti/HTools/Program/Hail.hs |    2 +-
 src/Ganeti/HTools/Program/Hbal.hs |    1 +
 4 files changed, 67 insertions(+), 12 deletions(-)

diff --git a/src/Ganeti/HTools/CLI.hs b/src/Ganeti/HTools/CLI.hs
index 12c3914..f4ca9fe 100644
--- a/src/Ganeti/HTools/CLI.hs
+++ b/src/Ganeti/HTools/CLI.hs
@@ -48,6 +48,7 @@ module Ganeti.HTools.CLI
   , oDiskTemplate
   , oSpindleUse
   , oDynuFile
+  , oMonDDataFile
   , oEvacMode
   , oExInst
   , oExTags
@@ -123,6 +124,8 @@ data Options = Options
   , optSpindleUse  :: Maybe Int      -- ^ Override for the spindle usage
   , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
   , optIgnoreDynu  :: Bool           -- ^ Do not use dynamic use data
+  , optMonDDataFile :: Maybe FilePath -- ^ Optional file with data provided
+                                     -- ^ by MonDs
   , optEvacMode    :: Bool           -- ^ Enable evacuation mode
   , optExInst      :: [String]       -- ^ Instances to be excluded
   , optExTags      :: Maybe [String] -- ^ Tags to use for exclusion
@@ -178,6 +181,7 @@ defaultOptions  = Options
   , optSpindleUse  = Nothing
   , optIgnoreDynu  = False
   , optDynuFile    = Nothing
+  , optMonDDataFile = Nothing
   , optEvacMode    = False
   , optExInst      = []
   , optExTags      = Nothing
@@ -280,6 +284,13 @@ oDiskMoves =
    \ thus allowing only the 'cheap' failover/migrate operations",
    OptComplNone)
 
+oMonDDataFile :: OptType
+oMonDDataFile =
+  (Option "" ["mond-data"]
+   (ReqArg (\ f opts -> Ok opts { optMonDDataFile = Just f }) "FILE")
+   "Import data provided by MonDs from the given FILE",
+   OptComplFile)
+
 oDiskTemplate :: OptType
 oDiskTemplate =
   (Option "" ["disk-template"]
diff --git a/src/Ganeti/HTools/ExtLoader.hs b/src/Ganeti/HTools/ExtLoader.hs
index d42c833..9086e91 100644
--- a/src/Ganeti/HTools/ExtLoader.hs
+++ b/src/Ganeti/HTools/ExtLoader.hs
@@ -47,6 +47,8 @@ import System.Time (getClockTime)
 import Text.Printf (hPrintf)
 
 import qualified Text.JSON as J
+import qualified Data.Map as Map
+import qualified Data.List as L
 
 import qualified Ganeti.Constants as C
 import qualified Ganeti.DataCollectors.CPUload as CPUload
@@ -131,7 +133,7 @@ loadExternalData opts = do
       ldresult = input_data >>= (if ignoreDynU then clearDynU else return)
                             >>= mergeData eff_u exTags selInsts exInsts now
   cdata <- exitIfBad "failed to load data, aborting" ldresult
-  cdata' <- if ignoreDynU then return cdata else queryAllMonDDCs cdata
+  cdata' <- if ignoreDynU then return cdata else queryAllMonDDCs cdata opts
   let (fix_msgs, nl) = checkData (cdNodes cdata') (cdInstances cdata')
 
   unless (optVerbose opts == 0) $ maybeShowWarnings fix_msgs
@@ -166,18 +168,46 @@ collectors :: [DataCollector]
 collectors =
   [ DataCollector CPUload.dcName CPUload.dcCategory ]
 
+-- | MonDs Data parsed by a mock file. Representing (node name, list of reports
+-- produced by MonDs Data Collectors).
+type MonDData = (String, [DCReport])
+
+-- | A map storing MonDs data.
+type MapMonDData = Map.Map String [DCReport]
+
+-- | Parse MonD data file contents.
+pMonDData :: String -> Result [MonDData]
+pMonDData input =
+  loadJSArray "Parsing MonD's answer" input >>=
+  mapM (pMonDN . J.fromJSObject)
+
+-- | Parse a node's JSON record.
+pMonDN :: JSRecord -> Result MonDData
+pMonDN a = do
+  node <- tryFromObj "Parsing node's name" a "node"
+  reports <- tryFromObj "Parsing node's reports" a "reports"
+  return (node, reports)
+
 -- | Query all MonDs for all Data Collector.
-queryAllMonDDCs :: ClusterData -> IO ClusterData
-queryAllMonDDCs cdata = do
+queryAllMonDDCs :: ClusterData -> Options -> IO ClusterData
+queryAllMonDDCs cdata opts = do
+  map_mDD <-
+    case optMonDDataFile opts of
+      Nothing -> return Nothing
+      Just fp -> do
+        monDData_contents <- readFile fp
+        monDData <- exitIfBad "can't parse MonD data"
+                    . pMonDData $ monDData_contents
+        return . Just $ Map.fromList monDData
   let (ClusterData _ nl il _ _) = cdata
-  (nl', il') <- foldM queryAllMonDs (nl, il) collectors
+  (nl', il') <- foldM (queryAllMonDs map_mDD) (nl, il) collectors
   return $ cdata {cdNodes = nl', cdInstances = il'}
 
 -- | Query all MonDs for a single Data Collector.
-queryAllMonDs :: (Node.List, Instance.List) -> DataCollector
-                 -> IO (Node.List, Instance.List)
-queryAllMonDs (nl, il) dc = do
-  elems <- mapM (queryAMonD dc) (Container.elems nl)
+queryAllMonDs :: Maybe MapMonDData -> (Node.List, Instance.List)
+                 -> DataCollector -> IO (Node.List, Instance.List)
+queryAllMonDs m (nl, il) dc = do
+  elems <- mapM (queryAMonD m dc) (Container.elems nl)
   let elems' = catMaybes elems
   if length elems == length elems'
     then do
@@ -214,10 +244,23 @@ mkReport dc dcr =
                 Bad _ -> Nothing
           | otherwise -> Nothing
 
+-- | Get data report for the specified Data Collector and Node from the map.
+fromFile :: DataCollector -> Node.Node -> MapMonDData -> Maybe DCReport
+fromFile dc node m =
+  case Map.lookup (Node.name node) m of
+    Nothing -> Nothing
+    Just reports ->
+      let matchDCName dcr = dName dc == dcReportName dcr
+      in L.find matchDCName reports
+
 -- | Query a MonD for a single Data Collector.
-queryAMonD :: DataCollector -> Node.Node -> IO (Maybe Node.Node)
-queryAMonD dc node = do
-  dcReport <- fromCurl dc node
+queryAMonD :: Maybe MapMonDData -> DataCollector -> Node.Node
+              -> IO (Maybe Node.Node)
+queryAMonD m dc node = do
+  dcReport <-
+    case m of
+      Nothing -> fromCurl dc node
+      Just m' -> return $ fromFile dc node m'
   case mkReport dc dcReport of
     Nothing -> return Nothing
     Just report ->
diff --git a/src/Ganeti/HTools/Program/Hail.hs 
b/src/Ganeti/HTools/Program/Hail.hs
index 507192c..b1e3653 100644
--- a/src/Ganeti/HTools/Program/Hail.hs
+++ b/src/Ganeti/HTools/Program/Hail.hs
@@ -74,7 +74,7 @@ wrapReadRequest opts args = do
     else do
       let Request rqt cdata = r1
       cdata' <-
-        if optIgnoreDynu opts then return cdata else queryAllMonDDCs cdata
+        if optIgnoreDynu opts then return cdata else queryAllMonDDCs cdata opts
       return $ Request rqt cdata'
 
 -- | Main function.
diff --git a/src/Ganeti/HTools/Program/Hbal.hs 
b/src/Ganeti/HTools/Program/Hbal.hs
index f863ad1..c385642 100644
--- a/src/Ganeti/HTools/Program/Hbal.hs
+++ b/src/Ganeti/HTools/Program/Hbal.hs
@@ -92,6 +92,7 @@ options = do
     , oInstMoves
     , oDynuFile
     , oIgnoreDyn 
+    , oMonDDataFile
     , oExTags
     , oExInst
     , oSaveCluster
-- 
1.7.10.4

Reply via email to