Lots of documentation updates
authorIustin Pop <iustin@google.com>
Fri, 29 May 2009 17:25:34 +0000 (19:25 +0200)
committerIustin Pop <iustin@google.com>
Mon, 1 Jun 2009 09:18:19 +0000 (11:18 +0200)
This patch does only doc build changes, doc changes and function move
around (for more logical documentation). It should have no impact at all
on the code.

14 files changed:
Ganeti/HTools/CLI.hs
Ganeti/HTools/Cluster.hs
Ganeti/HTools/Container.hs
Ganeti/HTools/IAlloc.hs
Ganeti/HTools/Instance.hs
Ganeti/HTools/Loader.hs
Ganeti/HTools/Node.hs
Ganeti/HTools/PeerMap.hs
Ganeti/HTools/Rapi.hs
Ganeti/HTools/Text.hs
Ganeti/HTools/Types.hs
Ganeti/HTools/Utils.hs
Ganeti/HTools/Version.hs.in
Makefile

index fea6037..6de77ee 100644 (file)
@@ -33,18 +33,26 @@ import qualified Ganeti.HTools.Node as Node
 
 import Ganeti.HTools.Types
 
--- | Class for types which support show help and show version
+-- | Class for types which support show help and show version.
 class CLIOptions a where
+    -- | Denotes whether the show help option has been passed.
     showHelp    :: a -> Bool
+    -- | Denotes whether the show version option has been passed.
     showVersion :: a -> Bool
 
--- | Class for types which support the -i/-n/-m options
+-- | Class for types which support the -i\/-n\/-m options.
 class EToolOptions a where
+    -- | Returns the node file name.
     nodeFile   :: a -> FilePath
+    -- | Tells whether the node file has been passed as an option.
     nodeSet    :: a -> Bool
+    -- | Returns the instance file name.
     instFile   :: a -> FilePath
+    -- | Tells whether the instance file has been passed as an option.
     instSet    :: a -> Bool
+    -- | Rapi target, if one has been passed.
     masterName :: a -> String
+    -- | Whether to be less verbose.
     silent     :: a -> Bool
 
 -- | Command line parser, using the 'options' structure.
@@ -75,15 +83,16 @@ parseOpts argv progname options defaultOptions =
       where header = printf "%s %s\nUsage: %s [OPTION...]"
                      progname Version.version progname
 
--- | Parse the environment and return the node/instance names.
--- This also hardcodes here the default node/instance file names.
+-- | Parse the environment and return the node\/instance names.
+--
+-- This also hardcodes here the default node\/instance file names.
 parseEnv :: () -> IO (String, String)
 parseEnv () = do
   a <- getEnvDefault "HTOOLS_NODES" "nodes"
   b <- getEnvDefault "HTOOLS_INSTANCES" "instances"
   return (a, b)
 
--- | A shell script template for autogenerated scripts
+-- | A shell script template for autogenerated scripts.
 shTemplate :: String
 shTemplate =
     printf "#!/bin/sh\n\n\
@@ -97,7 +106,7 @@ shTemplate =
            \  fi\n\
            \}\n\n"
 
--- | External tool data loader from a variety of sources
+-- | External tool data loader from a variety of sources.
 loadExternalData :: (EToolOptions a) =>
                     a
                  -> IO (Node.List, Instance.List, String)
index 9a952a4..a3d27c2 100644 (file)
@@ -47,25 +47,19 @@ import qualified Ganeti.HTools.Node as Node
 import Ganeti.HTools.Types
 import Ganeti.HTools.Utils
 
--- | A separate name for the cluster score type
+-- * Types
+
+-- | A separate name for the cluster score type.
 type Score = Double
 
 -- | The description of an instance placement.
 type Placement = (Idx, Ndx, Ndx, Score)
 
-{- | A cluster solution described as the solution delta and the list
-of placements.
-
--}
+-- | A cluster solution described as the solution delta and the list
+-- of placements.
 data Solution = Solution Int [Placement]
                 deriving (Eq, Ord, Show)
 
--- | Returns the delta of a solution or -1 for Nothing
-solutionDelta :: Maybe Solution -> Int
-solutionDelta sol = case sol of
-                      Just (Solution d _) -> d
-                      _ -> -1
-
 -- | A removal set.
 data Removal = Removal Node.List [Instance.Instance]
 
@@ -81,7 +75,13 @@ data IMove = Failover                -- ^ Failover the instance (f)
 data Table = Table Node.List Instance.List Score [Placement]
              deriving (Show)
 
--- General functions
+-- * Utility functions
+
+-- | Returns the delta of a solution or -1 for Nothing.
+solutionDelta :: Maybe Solution -> Int
+solutionDelta sol = case sol of
+                      Just (Solution d _) -> d
+                      _ -> -1
 
 -- | Cap the removal list if needed.
 capRemovals :: [a] -> Int -> [a]
@@ -99,7 +99,62 @@ verifyN1Check nl = any Node.failN1 nl
 verifyN1 :: [Node.Node] -> [Node.Node]
 verifyN1 nl = filter Node.failN1 nl
 
-{-| Add an instance and return the new node and instance maps. -}
+{-| Computes the pair of bad nodes and instances.
+
+The bad node list is computed via a simple 'verifyN1' check, and the
+bad instance list is the list of primary and secondary instances of
+those nodes.
+
+-}
+computeBadItems :: Node.List -> Instance.List ->
+                   ([Node.Node], [Instance.Instance])
+computeBadItems nl il =
+  let bad_nodes = verifyN1 $ filter (not . Node.offline) $ Container.elems nl
+      bad_instances = map (\idx -> Container.find idx il) $
+                      sort $ nub $ concat $
+                      map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
+  in
+    (bad_nodes, bad_instances)
+
+-- | Compute the total free disk and memory in the cluster.
+totalResources :: Container.Container Node.Node -> (Int, Int)
+totalResources nl =
+    foldl'
+    (\ (mem, dsk) node -> (mem + (Node.f_mem node),
+                           dsk + (Node.f_dsk node)))
+    (0, 0) (Container.elems nl)
+
+-- | Compute the mem and disk covariance.
+compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double)
+compDetailedCV nl =
+    let
+        all_nodes = Container.elems nl
+        (offline, nodes) = partition Node.offline all_nodes
+        mem_l = map Node.p_mem nodes
+        dsk_l = map Node.p_dsk nodes
+        mem_cv = varianceCoeff mem_l
+        dsk_cv = varianceCoeff dsk_l
+        n1_l = length $ filter Node.failN1 nodes
+        n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
+        res_l = map Node.p_rem nodes
+        res_cv = varianceCoeff res_l
+        offline_inst = sum . map (\n -> (length . Node.plist $ n) +
+                                        (length . Node.slist $ n)) $ offline
+        online_inst = sum . map (\n -> (length . Node.plist $ n) +
+                                       (length . Node.slist $ n)) $ nodes
+        off_score = (fromIntegral offline_inst) /
+                    (fromIntegral $ online_inst + offline_inst)
+    in (mem_cv, dsk_cv, n1_score, res_cv, off_score)
+
+-- | Compute the /total/ variance.
+compCV :: Node.List -> Double
+compCV nl =
+    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
+    in mem_cv + dsk_cv + n1_score + res_cv + off_score
+
+-- * hn1 functions
+
+-- | Add an instance and return the new node and instance maps.
 addInstance :: Node.List -> Instance.Instance ->
                Node.Node -> Node.Node -> Maybe Node.List
 addInstance nl idata pri sec =
@@ -128,15 +183,8 @@ removeInstance nl idata =
 removeInstances :: Node.List -> [Instance.Instance] -> Node.List
 removeInstances = foldl' removeInstance
 
--- | Compute the total free disk and memory in the cluster.
-totalResources :: Container.Container Node.Node -> (Int, Int)
-totalResources nl =
-    foldl'
-    (\ (mem, dsk) node -> (mem + (Node.f_mem node),
-                           dsk + (Node.f_dsk node)))
-    (0, 0) (Container.elems nl)
 
-{- | Compute a new version of a cluster given a solution.
+{-| Compute a new version of a cluster given a solution.
 
 This is not used for computing the solutions, but for applying a
 (known-good) solution to the original cluster for final display.
@@ -161,9 +209,9 @@ applySolution nl il sol =
            ) nc odxes
 
 
--- First phase functions
+-- ** First phase functions
 
-{- | Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
+{-| Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
     [3..n]), ...]
 
 -}
@@ -190,25 +238,7 @@ genNames count1 names1 =
   in
     aux_fn count1 names1 []
 
-{- | Computes the pair of bad nodes and instances.
-
-The bad node list is computed via a simple 'verifyN1' check, and the
-bad instance list is the list of primary and secondary instances of
-those nodes.
-
--}
-computeBadItems :: Node.List -> Instance.List ->
-                   ([Node.Node], [Instance.Instance])
-computeBadItems nl il =
-  let bad_nodes = verifyN1 $ filter (not . Node.offline) $ Container.elems nl
-      bad_instances = map (\idx -> Container.find idx il) $
-                      sort $ nub $ concat $
-                      map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
-  in
-    (bad_nodes, bad_instances)
-
-
-{- | Checks if removal of instances results in N+1 pass.
+{-| Checks if removal of instances results in N+1 pass.
 
 Note: the check removal cannot optimize by scanning only the affected
 nodes, since the cluster is known to be not healthy; only the check
@@ -226,7 +256,7 @@ checkRemoval nl victims =
       Just $ Removal nx victims
 
 
--- | Computes the removals list for a given depth
+-- | Computes the removals list for a given depth.
 computeRemovals :: Node.List
                  -> [Instance.Instance]
                  -> Int
@@ -234,9 +264,9 @@ computeRemovals :: Node.List
 computeRemovals nl bad_instances depth =
     map (checkRemoval nl) $ genNames depth bad_instances
 
--- Second phase functions
+-- ** Second phase functions
 
--- | Single-node relocation cost
+-- | Single-node relocation cost.
 nodeDelta :: Ndx -> Ndx -> Ndx -> Int
 nodeDelta i p s =
     if i == p || i == s then
@@ -244,22 +274,16 @@ nodeDelta i p s =
     else
         1
 
-{-| Compute best solution.
-
-    This function compares two solutions, choosing the minimum valid
-    solution.
--}
+-- | Compute best solution.
+--
+-- This function compares two solutions, choosing the minimum valid
+-- solution.
 compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
 compareSolutions a b = case (a, b) of
   (Nothing, x) -> x
   (x, Nothing) -> x
   (x, y) -> min x y
 
--- | Compute best table. Note that the ordering of the arguments is important.
-compareTables :: Table -> Table -> Table
-compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
-    if a_cv > b_cv then b else a
-
 -- | Check if a given delta is worse then an existing solution.
 tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
 tooHighDelta sol new_delta max_delta =
@@ -330,7 +354,66 @@ checkPlacement nl victims current current_delta prev_sol max_delta =
                 ) accu_p nodes
     ) prev_sol nodes
 
--- | Apply a move
+{-| Auxiliary function for solution computation.
+
+We write this in an explicit recursive fashion in order to control
+early-abort in case we have met the min delta. We can't use foldr
+instead of explicit recursion since we need the accumulator for the
+abort decision.
+
+-}
+advanceSolution :: [Maybe Removal] -- ^ The removal to process
+                -> Int             -- ^ Minimum delta parameter
+                -> Int             -- ^ Maximum delta parameter
+                -> Maybe Solution  -- ^ Current best solution
+                -> Maybe Solution  -- ^ New best solution
+advanceSolution [] _ _ sol = sol
+advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
+advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
+    let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
+        new_delta = solutionDelta $! new_sol
+    in
+      if new_delta >= 0 && new_delta <= min_d then
+          new_sol
+      else
+          advanceSolution xs min_d max_d new_sol
+
+-- | Computes the placement solution.
+solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
+                     -> Int             -- ^ Minimum delta parameter
+                     -> Int             -- ^ Maximum delta parameter
+                     -> Maybe Solution  -- ^ The best solution found
+solutionFromRemovals removals min_delta max_delta =
+    advanceSolution removals min_delta max_delta Nothing
+
+{-| Computes the solution at the given depth.
+
+This is a wrapper over both computeRemovals and
+solutionFromRemovals. In case we have no solution, we return Nothing.
+
+-}
+computeSolution :: Node.List        -- ^ The original node data
+                -> [Instance.Instance] -- ^ The list of /bad/ instances
+                -> Int             -- ^ The /depth/ of removals
+                -> Int             -- ^ Maximum number of removals to process
+                -> Int             -- ^ Minimum delta parameter
+                -> Int             -- ^ Maximum delta parameter
+                -> Maybe Solution  -- ^ The best solution found (or Nothing)
+computeSolution nl bad_instances depth max_removals min_delta max_delta =
+  let
+      removals = computeRemovals nl bad_instances depth
+      removals' = capRemovals removals max_removals
+  in
+    solutionFromRemovals removals' min_delta max_delta
+
+-- * hbal functions
+
+-- | Compute best table. Note that the ordering of the arguments is important.
+compareTables :: Table -> Table -> Table
+compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
+    if a_cv > b_cv then b else a
+
+-- | Applies an instance move to a given node list and instance.
 applyMove :: Node.List -> Instance.Instance
           -> IMove -> (Maybe Node.List, Instance.Instance, Ndx, Ndx)
 -- Failover (f)
@@ -407,6 +490,7 @@ applyMove nl inst (FailoverAndReplace new_sdx) =
                  Container.addTwo old_sdx new_p old_pdx int_p nl
     in (new_nl, Instance.setBoth inst old_sdx new_sdx, old_sdx, new_sdx)
 
+-- | Tries to allocate an instance on one given node.
 allocateOnSingle :: Node.List -> Instance.Instance -> Node.Node
                  -> (Maybe Node.List, Instance.Instance)
 allocateOnSingle nl inst p =
@@ -415,6 +499,7 @@ allocateOnSingle nl inst p =
                  return $ Container.add new_pdx new_p nl
     in (new_nl, Instance.setBoth inst new_pdx Node.noSecondary)
 
+-- | Tries to allocate an instance on a given pair of nodes.
 allocateOnPair :: Node.List -> Instance.Instance -> Node.Node -> Node.Node
                -> (Maybe Node.List, Instance.Instance)
 allocateOnPair nl inst tgt_p tgt_s =
@@ -426,6 +511,8 @@ allocateOnPair nl inst tgt_p tgt_s =
           return $ Container.addTwo new_pdx new_p new_sdx new_s nl
     in (new_nl, Instance.setBoth inst new_pdx new_sdx)
 
+-- | Tries to perform an instance move and returns the best table
+-- between the original one and the new one.
 checkSingleStep :: Table -- ^ The original table
                 -> Instance.Instance -- ^ The instance to move
                 -> Table -- ^ The current best table
@@ -502,59 +589,8 @@ checkMove nodes_idx ini_tbl victims =
       else
           best_tbl
 
-{- | Auxiliary function for solution computation.
-
-We write this in an explicit recursive fashion in order to control
-early-abort in case we have met the min delta. We can't use foldr
-instead of explicit recursion since we need the accumulator for the
-abort decision.
-
--}
-advanceSolution :: [Maybe Removal] -- ^ The removal to process
-                -> Int             -- ^ Minimum delta parameter
-                -> Int             -- ^ Maximum delta parameter
-                -> Maybe Solution  -- ^ Current best solution
-                -> Maybe Solution  -- ^ New best solution
-advanceSolution [] _ _ sol = sol
-advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
-advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
-    let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
-        new_delta = solutionDelta $! new_sol
-    in
-      if new_delta >= 0 && new_delta <= min_d then
-          new_sol
-      else
-          advanceSolution xs min_d max_d new_sol
-
--- | Computes the placement solution.
-solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
-                     -> Int             -- ^ Minimum delta parameter
-                     -> Int             -- ^ Maximum delta parameter
-                     -> Maybe Solution  -- ^ The best solution found
-solutionFromRemovals removals min_delta max_delta =
-    advanceSolution removals min_delta max_delta Nothing
-
-{- | Computes the solution at the given depth.
-
-This is a wrapper over both computeRemovals and
-solutionFromRemovals. In case we have no solution, we return Nothing.
 
--}
-computeSolution :: Node.List        -- ^ The original node data
-                -> [Instance.Instance] -- ^ The list of /bad/ instances
-                -> Int             -- ^ The /depth/ of removals
-                -> Int             -- ^ Maximum number of removals to process
-                -> Int             -- ^ Minimum delta parameter
-                -> Int             -- ^ Maximum delta parameter
-                -> Maybe Solution  -- ^ The best solution found (or Nothing)
-computeSolution nl bad_instances depth max_removals min_delta max_delta =
-  let
-      removals = computeRemovals nl bad_instances depth
-      removals' = capRemovals removals max_removals
-  in
-    solutionFromRemovals removals' min_delta max_delta
-
--- Solution display functions (pure)
+-- * Formatting functions
 
 -- | Given the original and final nodes, computes the relocation description.
 computeMoves :: String -- ^ The instance name
@@ -600,13 +636,14 @@ computeMoves i a b c d =
                       printf "migrate -f %s" i,
                       printf "replace-disks -n %s %s" d i])
 
-{-| Converts a placement to string format -}
-printSolutionLine :: Node.List
-                  -> Instance.List
-                  -> Int
-                  -> Int
-                  -> Placement
-                  -> Int
+-- | Converts a placement to string format.
+printSolutionLine :: Node.List     -- ^ The node list
+                  -> Instance.List -- ^ The instance list
+                  -> Int           -- ^ Maximum node name length
+                  -> Int           -- ^ Maximum instance name length
+                  -> Placement     -- ^ The current placement
+                  -> Int           -- ^ The index of the placement in
+                                   -- the solution
                   -> (String, [String])
 printSolutionLine nl il nmlen imlen plc pos =
     let
@@ -627,6 +664,8 @@ printSolutionLine nl il nmlen imlen plc pos =
        pmlen nstr c moves,
        cmds)
 
+-- | Given a list of commands, prefix them with @gnt-instance@ and
+-- also beautify the display a little.
 formatCmds :: [[String]] -> String
 formatCmds cmd_strs =
     unlines $
@@ -636,7 +675,7 @@ formatCmds cmd_strs =
         (map ("gnt-instance " ++) b)) $
         zip [1..] cmd_strs
 
-{-| Converts a solution to string format -}
+-- | Converts a solution to string format.
 printSolution :: Node.List
               -> Instance.List
               -> [Placement]
@@ -663,34 +702,7 @@ printNodes nl =
                  "pri" "sec" "p_fmem" "p_fdsk"
     in unlines $ (header:map helper snl)
 
--- | Compute the mem and disk covariance.
-compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double)
-compDetailedCV nl =
-    let
-        all_nodes = Container.elems nl
-        (offline, nodes) = partition Node.offline all_nodes
-        mem_l = map Node.p_mem nodes
-        dsk_l = map Node.p_dsk nodes
-        mem_cv = varianceCoeff mem_l
-        dsk_cv = varianceCoeff dsk_l
-        n1_l = length $ filter Node.failN1 nodes
-        n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
-        res_l = map Node.p_rem nodes
-        res_cv = varianceCoeff res_l
-        offline_inst = sum . map (\n -> (length . Node.plist $ n) +
-                                        (length . Node.slist $ n)) $ offline
-        online_inst = sum . map (\n -> (length . Node.plist $ n) +
-                                       (length . Node.slist $ n)) $ nodes
-        off_score = (fromIntegral offline_inst) /
-                    (fromIntegral $ online_inst + offline_inst)
-    in (mem_cv, dsk_cv, n1_score, res_cv, off_score)
-
--- | Compute the 'total' variance.
-compCV :: Node.List -> Double
-compCV nl =
-    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
-    in mem_cv + dsk_cv + n1_score + res_cv + off_score
-
+-- | Shows statistics for a given node list.
 printStats :: Node.List -> String
 printStats nl =
     let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
index 25bc64c..7e51123 100644 (file)
@@ -86,15 +86,15 @@ fold = IntMap.fold
 addTwo :: Key -> a -> Key -> a -> Container a -> Container a
 addTwo k1 v1 k2 v2 c = add k1 v1 $ add k2 v2 c
 
--- | Compute the name of an element in a container
+-- | Compute the name of an element in a container.
 nameOf :: (T.Element a) => Container a -> Key -> String
 nameOf c k = T.nameOf $ find k c
 
--- | Compute the maximum name length in an Element Container
+-- | Compute the maximum name length in an Element Container.
 maxNameLen :: (T.Element a) => Container a -> Int
 maxNameLen = maximum . map (length . T.nameOf) . elems
 
--- | Find an element by name in a Container; this is a very slow function
+-- | Find an element by name in a Container; this is a very slow function.
 findByName :: (T.Element a, Monad m) =>
               Container a -> String -> m Key
 findByName c n =
index bafde49..bbeb868 100644 (file)
@@ -11,12 +11,10 @@ module Ganeti.HTools.IAlloc
     ) where
 
 import Data.Either ()
---import Data.Maybe
 import Control.Monad
 import Text.JSON (JSObject, JSValue(JSBool, JSString, JSArray),
                   makeObj, encodeStrict, decodeStrict,
                   fromJSObject, toJSString)
---import Text.Printf (printf)
 import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.Instance as Instance
@@ -24,14 +22,22 @@ import Ganeti.HTools.Loader
 import Ganeti.HTools.Utils
 import Ganeti.HTools.Types
 
+-- | The request type.
 data RqType
-    = Allocate Instance.Instance Int
-    | Relocate Idx Int [Ndx]
+    = Allocate Instance.Instance Int -- ^ A new instance allocation
+    | Relocate Idx Int [Ndx]         -- ^ Move an instance to a new
+                                     -- secondary node
     deriving (Show)
 
+-- | A complete request, as received from Ganeti.
 data Request = Request RqType Node.List Instance.List String
     deriving (Show)
 
+-- | Parse the basic specifications of an instance.
+--
+-- Instances in the cluster instance list and the instance in an
+-- 'Allocate' request share some common properties, which are read by
+-- this function.
 parseBaseInstance :: String
                   -> JSObject JSValue
                   -> Result (String, Instance.Instance)
@@ -48,9 +54,10 @@ parseBaseInstance n a = do
   let running = "running"
   return $ (n, Instance.create n mem disk running 0 0)
 
-parseInstance :: NameAssoc
-              -> String
-              -> JSObject JSValue
+-- | Parses an instance as found in the cluster instance list.
+parseInstance :: NameAssoc        -- ^ The node name-to-index association list
+              -> String           -- ^ The name of the instance
+              -> JSObject JSValue -- ^ The JSON object
               -> Result (String, Instance.Instance)
 parseInstance ktn n a = do
     base <- parseBaseInstance n a
@@ -62,7 +69,10 @@ parseInstance ktn n a = do
              else (readEitherString $ head snodes) >>= lookupNode ktn n)
     return (n, Instance.setBoth (snd base) pidx sidx)
 
-parseNode :: String -> JSObject JSValue -> Result (String, Node.Node)
+-- | Parses a node as found in the cluster node list.
+parseNode :: String           -- ^ The node's name
+          -> JSObject JSValue -- ^ The JSON object
+          -> Result (String, Node.Node)
 parseNode n a = do
     let name = n
     offline <- fromObj "offline" a
@@ -79,7 +89,9 @@ parseNode n a = do
                         dtotal dfree (offline || drained))
     return (name, node)
 
-parseData :: String -> Result Request
+-- | Top-level parser.
+parseData :: String         -- ^ The JSON message as received from Ganeti
+          -> Result Request -- ^ A (possible valid) request
 parseData body = do
   decoded <- fromJResult $ decodeStrict body
   let obj = decoded
@@ -116,7 +128,11 @@ parseData body = do
         other -> fail $ ("Invalid request type '" ++ other ++ "'")
   return $ Request rqtype map_n map_i csf
 
-formatResponse :: Bool -> String -> [String] -> String
+-- | Formats the response into a valid IAllocator response message.
+formatResponse :: Bool     -- ^ Whether the request was successful
+               -> String   -- ^ Information text
+               -> [String] -- ^ The list of chosen nodes
+               -> String   -- ^ The JSON-formatted message
 formatResponse success info nodes =
     let
         e_success = ("success", JSBool success)
index cdb2920..1bade6e 100644 (file)
@@ -9,15 +9,18 @@ module Ganeti.HTools.Instance where
 import qualified Ganeti.HTools.Types as T
 import qualified Ganeti.HTools.Container as Container
 
-data Instance = Instance { name :: String   -- ^ the instance name
-                         , mem :: Int       -- ^ memory of the instance
-                         , dsk :: Int       -- ^ disk size of instance
-                         , running :: Bool  -- ^ whether the instance
+-- * Type declarations
+
+-- | The instance type
+data Instance = Instance { name :: String   -- ^ The instance name
+                         , mem :: Int       -- ^ Memory of the instance
+                         , dsk :: Int       -- ^ Disk size of instance
+                         , running :: Bool  -- ^ Whether the instance
                                             -- is running
-                         , run_st :: String -- ^ original (text) run status
-                         , pnode :: T.Ndx   -- ^ original primary node
-                         , snode :: T.Ndx   -- ^ original secondary node
-                         , idx :: T.Idx     -- ^ internal index for
+                         , run_st :: String -- ^ Original (text) run status
+                         , pnode :: T.Ndx   -- ^ Original primary node
+                         , snode :: T.Ndx   -- ^ Original secondary node
+                         , idx :: T.Idx     -- ^ Internal index for
                                             -- book-keeping
                          } deriving (Show)
 
@@ -27,12 +30,18 @@ instance T.Element Instance where
     setName = setName
     setIdx  = setIdx
 
--- | A simple name for the int, instance association list
+-- | A simple name for the int, instance association list.
 type AssocList = [(T.Idx, Instance)]
 
--- | A simple name for an instance map
+-- | A simple name for an instance map.
 type List = Container.Container Instance
 
+-- * Initialization
+
+-- | Create an instance.
+--
+-- Some parameters are not initialized by function, and must be set
+-- later (via 'setIdx' for example).
 create :: String -> Int -> Int -> String -> T.Ndx -> T.Ndx -> Instance
 create name_init mem_init dsk_init run_init pn sn =
     Instance {
@@ -49,6 +58,24 @@ create name_init mem_init dsk_init run_init pn sn =
           idx = -1
         }
 
+-- | Changes the index.
+--
+-- This is used only during the building of the data structures.
+setIdx :: Instance  -- ^ the original instance
+        -> T.Idx    -- ^ new index
+        -> Instance -- ^ the modified instance
+setIdx t i = t { idx = i }
+
+-- | Changes the name.
+--
+-- This is used only during the building of the data structures.
+setName :: Instance -- ^ The original instance
+        -> String   -- ^ New name
+        -> Instance
+setName t s = t { name = s }
+
+-- * Update functions
+
 -- | Changes the primary node of the instance.
 setPri :: Instance  -- ^ the original instance
         -> T.Ndx    -- ^ the new primary node
@@ -67,14 +94,3 @@ setBoth :: Instance  -- ^ the original instance
          -> T.Ndx    -- ^ new secondary node index
          -> Instance -- ^ the modified instance
 setBoth t p s = t { pnode = p, snode = s }
-
--- | Changes the index.
--- This is used only during the building of the data structures.
-setIdx :: Instance  -- ^ the original instance
-        -> T.Idx    -- ^ new index
-        -> Instance -- ^ the modified instance
-setIdx t i = t { idx = i }
-
--- | Changes the name
--- This is used only during the building of the data structures.
-setName t s = t { name = s }
index 2d39cb8..0683d38 100644 (file)
@@ -23,21 +23,21 @@ import qualified Ganeti.HTools.Node as Node
 
 import Ganeti.HTools.Types
 
--- | Lookups a node into an assoc list
+-- | Lookups a node into an assoc list.
 lookupNode :: (Monad m) => [(String, Ndx)] -> String -> String -> m Ndx
 lookupNode ktn inst node =
     case lookup node ktn of
       Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
       Just idx -> return idx
 
--- | Lookups an instance into an assoc list
+-- | Lookups an instance into an assoc list.
 lookupInstance :: (Monad m) => [(String, Idx)] -> String -> m Idx
 lookupInstance kti inst =
     case lookup inst kti of
       Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
       Just idx -> return idx
 
--- | Given a list of elements (and their names), assign indices to them
+-- | Given a list of elements (and their names), assign indices to them.
 assignIndices :: (Element a) =>
                  [(String, a)]
               -> (NameAssoc, [(Int, a)])
@@ -45,7 +45,7 @@ assignIndices =
     unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
           . zip [0..]
 
--- | For each instance, add its index to its primary and secondary nodes
+-- | For each instance, add its index to its primary and secondary nodes.
 fixNodes :: [(Ndx, Node.Node)]
          -> [(Idx, Instance.Instance)]
          -> [(Ndx, Node.Node)]
@@ -72,7 +72,7 @@ fixNodes nl il =
            ) nl il
 
 -- | Compute the longest common suffix of a list of strings that
--- | starts with a dot
+-- | starts with a dot.
 longestDomain :: [String] -> String
 longestDomain [] = ""
 longestDomain (x:xs) =
@@ -81,12 +81,12 @@ longestDomain (x:xs) =
                               else accu)
       "" $ filter (isPrefixOf ".") (tails x)
 
--- | Remove tail suffix from a string
+-- | Remove tail suffix from a string.
 stripSuffix :: Int -> String -> String
 stripSuffix sflen name = take ((length name) - sflen) name
 
-{-| Initializer function that loads the data from a node and list file
-    and massages it into the correct format. -}
+-- | Initializer function that loads the data from a node and instance
+-- list and massages it into the correct format.
 mergeData :: (Node.AssocList,
               Instance.AssocList) -- ^ Data from either Text.loadData
                                   -- or Rapi.loadData
@@ -105,7 +105,7 @@ mergeData (nl, il) = do
       sil = Container.map (\i -> setName i (stripSuffix csl $ nameOf i)) il3
   return (snl, sil, common_suffix)
 
--- | Check cluster data for consistency
+-- | Checks the cluster data for consistency.
 checkData :: Node.List -> Instance.List
           -> ([String], Node.List)
 checkData nl il =
index 76052d8..5e9f418 100644 (file)
@@ -18,13 +18,13 @@ module Ganeti.HTools.Node
     , setOffline
     , setXmem
     , setFmem
+    , setPri
+    , setSec
     -- * Instance (re)location
     , removePri
     , removeSec
     , addPri
     , addSec
-    , setPri
-    , setSec
     -- * Formatting
     , list
     -- * Misc stuff
@@ -41,24 +41,27 @@ import qualified Ganeti.HTools.PeerMap as PeerMap
 
 import qualified Ganeti.HTools.Types as T
 
-data Node = Node { name  :: String -- ^ the node name
-                 , t_mem :: Double -- ^ total memory (MiB)
-                 , n_mem :: Int    -- ^ node memory (MiB)
-                 , f_mem :: Int    -- ^ free memory (MiB)
-                 , x_mem :: Int    -- ^ unaccounted memory (MiB)
-                 , t_dsk :: Double -- ^ total disk space (MiB)
-                 , f_dsk :: Int    -- ^ free disk space (MiB)
-                 , plist :: [T.Idx]-- ^ list of primary instance indices
-                 , slist :: [T.Idx]-- ^ list of secondary instance indices
-                 , idx :: T.Ndx    -- ^ internal index for book-keeping
-                 , peers :: PeerMap.PeerMap -- ^ pnode to instance mapping
-                 , failN1:: Bool   -- ^ whether the node has failed n1
-                 , r_mem :: Int    -- ^ maximum memory needed for
+-- * Type declarations
+
+-- | The node type.
+data Node = Node { name  :: String -- ^ The node name
+                 , t_mem :: Double -- ^ Total memory (MiB)
+                 , n_mem :: Int    -- ^ Node memory (MiB)
+                 , f_mem :: Int    -- ^ Free memory (MiB)
+                 , x_mem :: Int    -- ^ Unaccounted memory (MiB)
+                 , t_dsk :: Double -- ^ Total disk space (MiB)
+                 , f_dsk :: Int    -- ^ Free disk space (MiB)
+                 , plist :: [T.Idx]-- ^ List of primary instance indices
+                 , slist :: [T.Idx]-- ^ List of secondary instance indices
+                 , idx :: T.Ndx    -- ^ Internal index for book-keeping
+                 , peers :: PeerMap.PeerMap -- ^ Pnode to instance mapping
+                 , failN1:: Bool   -- ^ Whether the node has failed n1
+                 , r_mem :: Int    -- ^ Maximum memory needed for
                                    -- failover by primaries of this node
-                 , p_mem :: Double -- ^ percent of free memory
-                 , p_dsk :: Double -- ^ percent of free disk
-                 , p_rem :: Double -- ^ percent of reserved memory
-                 , offline :: Bool -- ^ whether the node should not be used
+                 , p_mem :: Double -- ^ Percent of free memory
+                 , p_dsk :: Double -- ^ Percent of free disk
+                 , p_rem :: Double -- ^ Percent of reserved memory
+                 , offline :: Bool -- ^ Whether the node should not be used
                                    -- for allocations and skipped from
                                    -- score computations
   } deriving (Show)
@@ -69,22 +72,22 @@ instance T.Element Node where
     setName = setName
     setIdx = setIdx
 
--- | A simple name for the int, node association list
+-- | A simple name for the int, node association list.
 type AssocList = [(T.Ndx, Node)]
 
--- | A simple name for a node map
+-- | A simple name for a node map.
 type List = Container.Container Node
 
--- | Constant node index for a non-moveable instance
+-- | Constant node index for a non-moveable instance.
 noSecondary :: T.Ndx
 noSecondary = -1
 
-{- | Create a new node.
-
-The index and the peers maps are empty, and will be need to be update
-later via the 'setIdx' and 'buildPeers' functions.
+-- * Initialization functions
 
--}
+-- | Create a new node.
+--
+-- The index and the peers maps are empty, and will be need to be
+-- update later via the 'setIdx' and 'buildPeers' functions.
 create :: String -> Double -> Int -> Int -> Double -> Int -> Bool -> Node
 create name_init mem_t_init mem_n_init mem_f_init
        dsk_t_init dsk_f_init offline_init =
@@ -110,39 +113,25 @@ create name_init mem_t_init mem_n_init mem_f_init
     }
 
 -- | Changes the index.
+--
 -- This is used only during the building of the data structures.
 setIdx :: Node -> T.Ndx -> Node
 setIdx t i = t {idx = i}
 
--- | Changes the name
+-- | Changes the name.
+--
 -- This is used only during the building of the data structures.
+setName :: Node -> String -> Node
 setName t s = t {name = s}
 
--- | Sets the offline attribute
+-- | Sets the offline attribute.
 setOffline :: Node -> Bool -> Node
 setOffline t val = t { offline = val }
 
--- | Sets the unnaccounted memory
+-- | Sets the unnaccounted memory.
 setXmem :: Node -> Int -> Node
 setXmem t val = t { x_mem = val }
 
--- | Sets the free memory
-setFmem :: Node -> Int -> Node
-setFmem t new_mem =
-    let new_n1 = computeFailN1 (r_mem t) new_mem (f_dsk t)
-        new_mp = (fromIntegral new_mem) / (t_mem t)
-    in
-      t { f_mem = new_mem, failN1 = new_n1, p_mem = new_mp }
-
--- | Given the rmem, free memory and disk, computes the failn1 status.
-computeFailN1 :: Int -> Int -> Int -> Bool
-computeFailN1 new_rmem new_mem new_dsk =
-    new_mem <= new_rmem || new_dsk <= 0
-
--- | Given the new free memory and disk, fail if any of them is below zero.
-failHealth :: Int -> Int -> Bool
-failHealth new_mem new_dsk = new_mem <= 0 || new_dsk <= 0
-
 -- | Computes the maximum reserved memory for peers from a peer map.
 computeMaxRes :: PeerMap.PeerMap -> PeerMap.Elem
 computeMaxRes new_peers = PeerMap.maxElem new_peers
@@ -160,6 +149,33 @@ buildPeers t il =
         new_prem = (fromIntegral new_rmem) / (t_mem t)
     in t {peers=pmap, failN1 = new_failN1, r_mem = new_rmem, p_rem = new_prem}
 
+-- | Assigns an instance to a node as primary without other updates.
+setPri :: Node -> T.Idx -> Node
+setPri t idx = t { plist = idx:(plist t) }
+
+-- | Assigns an instance to a node as secondary without other updates.
+setSec :: Node -> T.Idx -> Node
+setSec t idx = t { slist = idx:(slist t) }
+
+-- * Update functions
+
+-- | Sets the free memory.
+setFmem :: Node -> Int -> Node
+setFmem t new_mem =
+    let new_n1 = computeFailN1 (r_mem t) new_mem (f_dsk t)
+        new_mp = (fromIntegral new_mem) / (t_mem t)
+    in
+      t { f_mem = new_mem, failN1 = new_n1, p_mem = new_mp }
+
+-- | Given the rmem, free memory and disk, computes the failn1 status.
+computeFailN1 :: Int -> Int -> Int -> Bool
+computeFailN1 new_rmem new_mem new_dsk =
+    new_mem <= new_rmem || new_dsk <= 0
+
+-- | Given the new free memory and disk, fail if any of them is below zero.
+failHealth :: Int -> Int -> Bool
+failHealth new_mem new_dsk = new_mem <= 0 || new_dsk <= 0
+
 -- | Removes a primary instance.
 removePri :: Node -> Instance.Instance -> Node
 removePri t inst =
@@ -236,13 +252,7 @@ addSec t inst pdx =
                 r_mem = new_rmem, p_dsk = new_dp,
                 p_rem = new_prem}
 
--- | Add a primary instance to a node without other updates
-setPri :: Node -> T.Idx -> Node
-setPri t idx = t { plist = idx:(plist t) }
-
--- | Add a secondary instance to a node without other updates
-setSec :: Node -> T.Idx -> Node
-setSec t idx = t { slist = idx:(slist t) }
+-- * Display functions
 
 -- | String converter for the node list functionality.
 list :: Int -> Node -> String
index 5bef2a5..a6d01d5 100644 (file)
@@ -30,15 +30,17 @@ type Key = Ndx
 type Elem = Int
 type PeerMap = [(Key, Elem)]
 
--- | Create a new empty map
+-- * Initialization functions
+
+-- | Create a new empty map.
 empty :: PeerMap
 empty = []
 
--- | Our reverse-compare function
+-- | Our reverse-compare function.
 pmCompare :: (Key, Elem) -> (Key, Elem) -> Ordering
 pmCompare a b = (compare `on` snd) b a
 
--- | Add or update (via a custom function) an element
+-- | Add or update (via a custom function) an element.
 addWith :: (Elem -> Elem -> Elem) -> Key -> Elem -> PeerMap -> PeerMap
 addWith fn k v lst =
     let r = lookup k lst
@@ -56,19 +58,26 @@ accumArray fn lst =
       [] -> empty
       (k, v):xs -> addWith fn k v $ accumArray fn xs
 
+-- * Basic operations
+
+-- | Returns either the value for a key or zero if not found
 find :: Key -> PeerMap -> Elem
 find k c = fromMaybe 0 $ lookup k c
 
+-- | Add an element to a peermap, overwriting the previous value
 add :: Key -> Elem -> PeerMap -> PeerMap
 add k v c = addWith (flip const) k v c
 
+-- | Remove an element from a peermap
 remove :: Key -> PeerMap -> PeerMap
 remove k c = case c of
                [] -> []
                (x@(x', _)):xs -> if k == x' then xs
                             else x:(remove k xs)
 
--- | Find the maximum element. Since this is a sorted list, we just
--- get the first one
+-- | Find the maximum element.
+--
+-- Since this is a sorted list, we just get the value at the head of
+-- the list, or zero for a null list
 maxElem :: PeerMap -> Elem
 maxElem c = if null c then 0 else snd . head $ c
index bdf2dff..28d2684 100644 (file)
@@ -21,7 +21,7 @@ import Ganeti.HTools.Types
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.Instance as Instance
 
--- | Read an URL via curl and return the body if successful
+-- | Read an URL via curl and return the body if successful.
 getUrl :: (Monad m) => String -> IO (m String)
 getUrl url = do
   (code, body) <- curlGetString url [CurlSSLVerifyPeer False,
@@ -31,12 +31,13 @@ getUrl url = do
             _ -> fail $ printf "Curl error for '%s', error %s"
                  url (show code))
 
--- | Append the default port if not passed in
+-- | Append the default port if not passed in.
 formatHost :: String -> String
 formatHost master =
     if elem ':' master then  master
     else "https://" ++ master ++ ":5080"
 
+-- | Parse a instance list in JSON format.
 getInstances :: NameAssoc
              -> String
              -> Result [(String, Instance.Instance)]
@@ -45,12 +46,14 @@ getInstances ktn body = do
   ilist <- mapM (parseInstance ktn) arr
   return ilist
 
+-- | Parse a node list in JSON format.
 getNodes :: String -> Result [(String, Node.Node)]
 getNodes body = do
   arr <- loadJSArray body
   nlist <- mapM parseNode arr
   return nlist
 
+-- | Construct an instance from a JSON object.
 parseInstance :: [(String, Ndx)]
               -> JSObject JSValue
               -> Result (String, Instance.Instance)
@@ -66,6 +69,7 @@ parseInstance ktn a = do
   let inst = Instance.create name mem disk running pnode snode
   return (name, inst)
 
+-- | Construct a node from a JSON object.
 parseNode :: JSObject JSValue -> Result (String, Node.Node)
 parseNode a = do
     name <- fromObj "name" a
@@ -83,6 +87,7 @@ parseNode a = do
                         dtotal dfree (offline || drained))
     return (name, node)
 
+-- | Builds the cluster data from an URL.
 loadData :: String -- ^ Cluster or URL to use as source
          -> IO (Result (Node.AssocList, Instance.AssocList))
 loadData master = do -- IO monad
index 19826cc..ecde661 100644 (file)
@@ -16,7 +16,7 @@ import Ganeti.HTools.Types
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.Instance as Instance
 
--- | Safe 'read' function returning data encapsulated in a Result
+-- | Safe 'read' function returning data encapsulated in a Result.
 tryRead :: (Monad m, Read a) => String -> String -> m a
 tryRead name s =
     let sols = readsPrec 0 s
@@ -26,7 +26,7 @@ tryRead name s =
                       ++ s ++ "': '" ++ e ++ "'"
          _ -> fail $ name ++ ": cannot parse string '" ++ s ++ "'"
 
--- | Load a node from a field list
+-- | Load a node from a field list.
 loadNode :: (Monad m) => [String] -> m (String, Node.Node)
 loadNode (name:tm:nm:fm:td:fd:fo:[]) = do
   new_node <-
@@ -42,7 +42,7 @@ loadNode (name:tm:nm:fm:td:fd:fo:[]) = do
   return (name, new_node)
 loadNode s = fail $ "Invalid/incomplete node data: '" ++ (show s) ++ "'"
 
--- | Load an instance from a field list
+-- | Load an instance from a field list.
 loadInst :: (Monad m) =>
             [(String, Ndx)] -> [String] -> m (String, Instance.Instance)
 loadInst ktn (name:mem:dsk:status:pnode:snode:[]) = do
@@ -57,13 +57,11 @@ loadInst ktn (name:mem:dsk:status:pnode:snode:[]) = do
   return (name, newinst)
 loadInst _ s = fail $ "Invalid/incomplete instance data: '" ++ (show s) ++ "'"
 
-{- | Convert newline and delimiter-separated text.
-
-This function converts a text in tabular format as generated by
-@gnt-instance list@ and @gnt-node list@ to a list of objects using a
-supplied conversion function.
-
--}
+-- | Convert newline and delimiter-separated text.
+--
+-- This function converts a text in tabular format as generated by
+-- @gnt-instance list@ and @gnt-node list@ to a list of objects using
+-- a supplied conversion function.
 loadTabular :: (Monad m, Element a) =>
                String -> ([String] -> m (String, a))
             -> m ([(String, Int)], [(Int, a)])
@@ -73,6 +71,7 @@ loadTabular text_data convert_fn = do
   kerows <- mapM convert_fn rows
   return $ assignIndices kerows
 
+-- | Builds the cluster data from node\/instance files.
 loadData :: String -- ^ Node data in string format
          -> String -- ^ Instance data in string format
          -> IO (Result (Node.AssocList, Instance.AssocList))
index 3066f31..e33ab0c 100644 (file)
@@ -5,13 +5,13 @@
 module Ganeti.HTools.Types
     where
 
--- | The instance index type
+-- | The instance index type.
 type Idx = Int
 
--- | The node index type
+-- | The node index type.
 type Ndx = Int
 
--- | The type used to hold name-to-idx mappings
+-- | The type used to hold name-to-idx mappings.
 type NameAssoc = [(String, Int)]
 
 {-|
@@ -32,9 +32,13 @@ instance Monad Result where
     return = Ok
     fail = Bad
 
--- | A generic class for items that have names and indices
+-- | A generic class for items that have updateable names and indices.
 class Element a where
+    -- | Returns the name of the element
     nameOf  :: a -> String
+    -- | Returns the index of the element
     idxOf   :: a -> Int
+    -- | Updates the name of the element
     setName :: a -> String -> a
+    -- | Updates the index of the element
     setIdx  :: a -> Int -> a
index 89cf3cb..459dc92 100644 (file)
@@ -26,14 +26,13 @@ import Ganeti.HTools.Types
 
 import Debug.Trace
 
+-- * Debug functions
+
 -- | To be used only for debugging, breaks referential integrity.
 debug :: Show a => a -> a
 debug x = trace (show x) x
 
-
-fromJResult :: Monad m => J.Result a -> m a
-fromJResult (J.Error x) = fail x
-fromJResult (J.Ok x) = return x
+-- * Miscelaneous
 
 -- | Comma-join a string list.
 commaJoin :: [String] -> String
@@ -53,6 +52,8 @@ sepSplit sep s
 commaSplit :: String -> [String]
 commaSplit = sepSplit ','
 
+-- * Mathematical functions
+
 -- Simple and slow statistical functions, please replace with better versions
 
 -- | Mean value of a list.
@@ -72,7 +73,7 @@ stdDev lst =
 varianceCoeff :: Floating a => [a] -> a
 varianceCoeff lst = (stdDev lst) / (fromIntegral $ length lst)
 
--- | Get an Ok result or print the error and exit
+-- | Get an Ok result or print the error and exit.
 readData :: Result a -> IO a
 readData nd =
     (case nd of
@@ -81,24 +82,39 @@ readData nd =
          exitWith $ ExitFailure 1
        Ok x -> return x)
 
+-- * JSON-related functions
+
+-- | Converts a JSON Result into a monadic value.
+fromJResult :: Monad m => J.Result a -> m a
+fromJResult (J.Error x) = fail x
+fromJResult (J.Ok x) = return x
+
+-- | Tries to read a string from a JSON value.
+--
+-- In case the value was not a string, we fail the read (in the
+-- context of the current monad.
 readEitherString :: (Monad m) => J.JSValue -> m String
 readEitherString v =
     case v of
       J.JSString s -> return $ J.fromJSString s
       _ -> fail "Wrong JSON type"
 
+-- | Converts a JSON message into an array of JSON objects.
 loadJSArray :: (Monad m) => String -> m [J.JSObject J.JSValue]
 loadJSArray s = fromJResult $ J.decodeStrict s
 
+-- | Reads a the value of a key in a JSON object.
 fromObj :: (J.JSON a, Monad m) => String -> J.JSObject J.JSValue -> m a
 fromObj k o =
     case lookup k (J.fromJSObject o) of
       Nothing -> fail $ printf "key '%s' not found in %s" k (show o)
       Just val -> fromJResult $ J.readJSON val
 
+-- | Converts a JSON value into a JSON object.
 asJSObject :: (Monad m) => J.JSValue -> m (J.JSObject J.JSValue)
 asJSObject (J.JSObject a) = return a
 asJSObject _ = fail "not an object"
 
+-- | Coneverts a list of JSON values into a list of JSON objects.
 asObjectList :: (Monad m) => [J.JSValue] -> m [J.JSObject J.JSValue]
 asObjectList = sequence . map asJSObject
index 8bd4a73..df4fd72 100644 (file)
@@ -5,5 +5,6 @@ module Ganeti.HTools.Version
       version
     ) where
 
--- | The version of the tree
+-- | The version of the sources.
+version :: String
 version = "(htools) version %ver%"
index 3b2df00..ae50834 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -22,8 +22,8 @@ test: HEXTRA=-fhpc
 $(DOCS) : %.html : %
        rst2html $< $@
 
-doc: $(DOCS)
-       rm -rf $(HDDIR)
+doc: $(DOCS) Ganeti/HTools/Version.hs
+       rm -rf $(HDDIR)/*
        mkdir -p $(HDDIR)/Ganeti/HTools
        cp hscolour.css $(HDDIR)/Ganeti/HTools
        for file in $(HSRCS); do \