commit 9d3fada5135467550adfb01a8fb9d34f148a748e
Author: Iustin Pop <iustin@google.com>
Date:   Thu May 21 00:37:03 2009 +0100

    Add initial validation checks in Cluster.loadData
    
    This patch converts loadTabular and loadData to a monadic form, thus
    allowing meaningful error messages from the node/instance load routines.

diff --git a/Ganeti/HTools/Cluster.hs b/Ganeti/HTools/Cluster.hs
index 73060a1..77237f7 100644
--- a/Ganeti/HTools/Cluster.hs
+++ b/Ganeti/HTools/Cluster.hs
@@ -40,6 +40,7 @@ import Data.List
 import Data.Maybe (isNothing, fromJust)
 import Text.Printf (printf)
 import Data.Function
+import Control.Monad
 
 import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Instance as Instance
@@ -694,15 +695,15 @@ This function converts a text in tabular format as generated by
 supplied conversion function.
 
 -}
-loadTabular :: String -> ([String] -> (String, a))
-            -> (a -> Int -> a) -> ([(String, Int)], [(Int, a)])
-loadTabular text_data convert_fn set_fn =
-    let lines_data = lines text_data
-        rows = map (sepSplit '|') lines_data
-        kerows = (map convert_fn rows)
-        idxrows = map (\ (idx, (k, v)) -> ((k, idx), (idx, set_fn v idx)))
-                  (zip [0..] kerows)
-    in unzip idxrows
+loadTabular :: (Monad m) => String -> ([String] -> m (String, a))
+            -> (a -> Int -> a) -> m ([(String, Int)], [(Int, a)])
+loadTabular text_data convert_fn set_fn = do
+  let lines_data = lines text_data
+      rows = map (sepSplit '|') lines_data
+  kerows <- mapM convert_fn rows
+  let idxrows = map (\ (idx, (k, v)) -> ((k, idx), (idx, set_fn v idx)))
+                (zip [0..] kerows)
+  return $ unzip idxrows
 
 -- | For each instance, add its index to its primary and secondary nodes
 fixNodes :: [(Int, Node.Node)]
@@ -742,6 +743,13 @@ stripSuffix suffix lst =
     let sflen = length suffix in
     map (\ (key, name) -> (key, take ((length name) - sflen) name)) lst
 
+-- | Lookups a node into an assoc list
+lookupNode :: (Monad m) => String -> String -> [(String, Int)] -> m Int
+lookupNode node inst ktn =
+    case lookup node ktn of
+      Nothing -> fail $ "Unknown node " ++ node ++ " for instance " ++ inst
+      Just idx -> return idx
+
 {-| Initializer function that loads the data from a node and list file
     and massages it into the correct format. -}
 loadData :: String -- ^ Node data in text format
@@ -749,39 +757,39 @@ loadData :: String -- ^ Node data in text format
          -> Result (Container.Container Node.Node,
                     Container.Container Instance.Instance,
                     String, NameList, NameList)
-loadData ndata idata =
-    let
-    {- node file: name t_mem n_mem f_mem t_disk f_disk -}
-        (ktn, nl) = loadTabular ndata
-                    (\ (name:tm:nm:fm:td:fd:fo:[]) ->
-                         (name,
-                          if any (== "?") [tm,nm,fm,td,fd] || fo == "Y" then
-                              Node.create 0 0 0 0 0 True
-                          else
-                              Node.create (read tm) (read nm) (read fm)
-                                      (read td) (read fd) False
-                         ))
-                    Node.setIdx
-    {- instance file: name mem disk status pnode snode -}
-        (kti, il) = loadTabular idata
-                    (\ (name:mem:dsk:status:pnode:snode:[]) ->
-                         (name,
-                          Instance.create (read mem) (read dsk)
-                              status
-                              (fromJust $ lookup pnode ktn)
-                              (fromJust $ lookup snode ktn)))
-                    Instance.setIdx
-        nl2 = fixNodes nl il
-        il3 = Container.fromAssocList il
-        nl3 = Container.fromAssocList
-             (map (\ (k, v) -> (k, Node.buildPeers v il3 (length nl2))) nl2)
-        xtn = swapPairs ktn
-        xti = swapPairs kti
-        common_suffix = longestDomain (xti ++ xtn)
-        stn = stripSuffix common_suffix xtn
-        sti = stripSuffix common_suffix xti
-    in
-      Ok (nl3, il3, common_suffix, stn, sti)
+loadData ndata idata = do
+  {- node file: name t_mem n_mem f_mem t_disk f_disk -}
+  (ktn, nl) <- loadTabular ndata
+               (\ (name:tm:nm:fm:td:fd:fo:[]) ->
+                    return (name,
+                            if any (== "?") [tm,nm,fm,td,fd] || fo == "Y" then
+                                Node.create 0 0 0 0 0 True
+                            else
+                                Node.create (read tm) (read nm) (read fm)
+                                        (read td) (read fd) False
+                           ))
+               Node.setIdx
+      {- instance file: name mem disk status pnode snode -}
+  (kti, il) <- loadTabular idata
+                  (\ (name:mem:dsk:status:pnode:snode:[]) -> do
+                     pidx <- lookupNode pnode name ktn
+                     sidx <- lookupNode snode name ktn
+                     let newinst = Instance.create (read mem) (read dsk)
+                                   status pidx sidx
+                     return (name, newinst)
+                  )
+                  Instance.setIdx
+  let
+      nl2 = fixNodes nl il
+      il3 = Container.fromAssocList il
+      nl3 = Container.fromAssocList
+            (map (\ (k, v) -> (k, Node.buildPeers v il3 (length nl2))) nl2)
+      xtn = swapPairs ktn
+      xti = swapPairs kti
+      common_suffix = longestDomain (xti ++ xtn)
+      stn = stripSuffix common_suffix xtn
+      sti = stripSuffix common_suffix xti
+  return (nl3, il3, common_suffix, stn, sti)
 
 -- | Compute the amount of memory used by primary instances on a node.
 nodeImem :: Node.Node -> InstanceList -> Int
