Revision 9188aeef

b/Ganeti/HTools/CLI.hs
33 33

  
34 34
import Ganeti.HTools.Types
35 35

  
36
-- | Class for types which support show help and show version
36
-- | Class for types which support show help and show version.
37 37
class CLIOptions a where
38
    -- | Denotes whether the show help option has been passed.
38 39
    showHelp    :: a -> Bool
40
    -- | Denotes whether the show version option has been passed.
39 41
    showVersion :: a -> Bool
40 42

  
41
-- | Class for types which support the -i/-n/-m options
43
-- | Class for types which support the -i\/-n\/-m options.
42 44
class EToolOptions a where
45
    -- | Returns the node file name.
43 46
    nodeFile   :: a -> FilePath
47
    -- | Tells whether the node file has been passed as an option.
44 48
    nodeSet    :: a -> Bool
49
    -- | Returns the instance file name.
45 50
    instFile   :: a -> FilePath
51
    -- | Tells whether the instance file has been passed as an option.
46 52
    instSet    :: a -> Bool
53
    -- | Rapi target, if one has been passed.
47 54
    masterName :: a -> String
55
    -- | Whether to be less verbose.
48 56
    silent     :: a -> Bool
49 57

  
50 58
-- | Command line parser, using the 'options' structure.
......
75 83
      where header = printf "%s %s\nUsage: %s [OPTION...]"
76 84
                     progname Version.version progname
77 85

  
78
-- | Parse the environment and return the node/instance names.
79
-- This also hardcodes here the default node/instance file names.
86
-- | Parse the environment and return the node\/instance names.
87
--
88
-- This also hardcodes here the default node\/instance file names.
80 89
parseEnv :: () -> IO (String, String)
81 90
parseEnv () = do
82 91
  a <- getEnvDefault "HTOOLS_NODES" "nodes"
83 92
  b <- getEnvDefault "HTOOLS_INSTANCES" "instances"
84 93
  return (a, b)
85 94

  
86
-- | A shell script template for autogenerated scripts
95
-- | A shell script template for autogenerated scripts.
87 96
shTemplate :: String
88 97
shTemplate =
89 98
    printf "#!/bin/sh\n\n\
......
97 106
           \  fi\n\
98 107
           \}\n\n"
99 108

  
100
-- | External tool data loader from a variety of sources
109
-- | External tool data loader from a variety of sources.
101 110
loadExternalData :: (EToolOptions a) =>
102 111
                    a
103 112
                 -> IO (Node.List, Instance.List, String)
b/Ganeti/HTools/Cluster.hs
47 47
import Ganeti.HTools.Types
48 48
import Ganeti.HTools.Utils
49 49

  
50
-- | A separate name for the cluster score type
50
-- * Types
51

  
52
-- | A separate name for the cluster score type.
51 53
type Score = Double
52 54

  
53 55
-- | The description of an instance placement.
54 56
type Placement = (Idx, Ndx, Ndx, Score)
55 57

  
56
{- | A cluster solution described as the solution delta and the list
57
of placements.
58

  
59
-}
58
-- | A cluster solution described as the solution delta and the list
59
-- of placements.
60 60
data Solution = Solution Int [Placement]
61 61
                deriving (Eq, Ord, Show)
62 62

  
63
-- | Returns the delta of a solution or -1 for Nothing
64
solutionDelta :: Maybe Solution -> Int
65
solutionDelta sol = case sol of
66
                      Just (Solution d _) -> d
67
                      _ -> -1
68

  
69 63
-- | A removal set.
70 64
data Removal = Removal Node.List [Instance.Instance]
71 65

  
......
81 75
data Table = Table Node.List Instance.List Score [Placement]
82 76
             deriving (Show)
83 77

  
84
-- General functions
78
-- * Utility functions
79

  
80
-- | Returns the delta of a solution or -1 for Nothing.
81
solutionDelta :: Maybe Solution -> Int
82
solutionDelta sol = case sol of
83
                      Just (Solution d _) -> d
84
                      _ -> -1
85 85

  
86 86
-- | Cap the removal list if needed.
87 87
capRemovals :: [a] -> Int -> [a]
......
99 99
verifyN1 :: [Node.Node] -> [Node.Node]
100 100
verifyN1 nl = filter Node.failN1 nl
101 101

  
102
{-| Add an instance and return the new node and instance maps. -}
102
{-| Computes the pair of bad nodes and instances.
103

  
104
The bad node list is computed via a simple 'verifyN1' check, and the
105
bad instance list is the list of primary and secondary instances of
106
those nodes.
107

  
108
-}
109
computeBadItems :: Node.List -> Instance.List ->
110
                   ([Node.Node], [Instance.Instance])
111
computeBadItems nl il =
112
  let bad_nodes = verifyN1 $ filter (not . Node.offline) $ Container.elems nl
113
      bad_instances = map (\idx -> Container.find idx il) $
114
                      sort $ nub $ concat $
115
                      map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
116
  in
117
    (bad_nodes, bad_instances)
118

  
119
-- | Compute the total free disk and memory in the cluster.
120
totalResources :: Container.Container Node.Node -> (Int, Int)
121
totalResources nl =
122
    foldl'
123
    (\ (mem, dsk) node -> (mem + (Node.f_mem node),
124
                           dsk + (Node.f_dsk node)))
125
    (0, 0) (Container.elems nl)
126

  
127
-- | Compute the mem and disk covariance.
128
compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double)
129
compDetailedCV nl =
130
    let
131
        all_nodes = Container.elems nl
132
        (offline, nodes) = partition Node.offline all_nodes
133
        mem_l = map Node.p_mem nodes
134
        dsk_l = map Node.p_dsk nodes
135
        mem_cv = varianceCoeff mem_l
136
        dsk_cv = varianceCoeff dsk_l
137
        n1_l = length $ filter Node.failN1 nodes
138
        n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
139
        res_l = map Node.p_rem nodes
140
        res_cv = varianceCoeff res_l
141
        offline_inst = sum . map (\n -> (length . Node.plist $ n) +
142
                                        (length . Node.slist $ n)) $ offline
143
        online_inst = sum . map (\n -> (length . Node.plist $ n) +
144
                                       (length . Node.slist $ n)) $ nodes
145
        off_score = (fromIntegral offline_inst) /
146
                    (fromIntegral $ online_inst + offline_inst)
147
    in (mem_cv, dsk_cv, n1_score, res_cv, off_score)
148

  
149
-- | Compute the /total/ variance.
150
compCV :: Node.List -> Double
151
compCV nl =
152
    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
153
    in mem_cv + dsk_cv + n1_score + res_cv + off_score
154

  
155
-- * hn1 functions
156

  
157
-- | Add an instance and return the new node and instance maps.
103 158
addInstance :: Node.List -> Instance.Instance ->
104 159
               Node.Node -> Node.Node -> Maybe Node.List
105 160
addInstance nl idata pri sec =
......
128 183
removeInstances :: Node.List -> [Instance.Instance] -> Node.List
129 184
removeInstances = foldl' removeInstance
130 185

  
131
-- | Compute the total free disk and memory in the cluster.
132
totalResources :: Container.Container Node.Node -> (Int, Int)
133
totalResources nl =
134
    foldl'
135
    (\ (mem, dsk) node -> (mem + (Node.f_mem node),
136
                           dsk + (Node.f_dsk node)))
137
    (0, 0) (Container.elems nl)
138 186

  
139
{- | Compute a new version of a cluster given a solution.
187
{-| Compute a new version of a cluster given a solution.
140 188

  
141 189
This is not used for computing the solutions, but for applying a
142 190
(known-good) solution to the original cluster for final display.
......
161 209
           ) nc odxes
162 210

  
163 211

  
164
-- First phase functions
212
-- ** First phase functions
165 213

  
166
{- | Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
214
{-| Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
167 215
    [3..n]), ...]
168 216

  
169 217
-}
......
190 238
  in
191 239
    aux_fn count1 names1 []
192 240

  
193
{- | Computes the pair of bad nodes and instances.
194

  
195
The bad node list is computed via a simple 'verifyN1' check, and the
196
bad instance list is the list of primary and secondary instances of
197
those nodes.
198

  
199
-}
200
computeBadItems :: Node.List -> Instance.List ->
201
                   ([Node.Node], [Instance.Instance])
202
computeBadItems nl il =
203
  let bad_nodes = verifyN1 $ filter (not . Node.offline) $ Container.elems nl
204
      bad_instances = map (\idx -> Container.find idx il) $
205
                      sort $ nub $ concat $
206
                      map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
207
  in
208
    (bad_nodes, bad_instances)
209

  
210

  
211
{- | Checks if removal of instances results in N+1 pass.
241
{-| Checks if removal of instances results in N+1 pass.
212 242

  
213 243
Note: the check removal cannot optimize by scanning only the affected
214 244
nodes, since the cluster is known to be not healthy; only the check
......
226 256
      Just $ Removal nx victims
227 257

  
228 258

  
229
-- | Computes the removals list for a given depth
259
-- | Computes the removals list for a given depth.
230 260
computeRemovals :: Node.List
231 261
                 -> [Instance.Instance]
232 262
                 -> Int
......
234 264
computeRemovals nl bad_instances depth =
235 265
    map (checkRemoval nl) $ genNames depth bad_instances
236 266

  
237
-- Second phase functions
267
-- ** Second phase functions
238 268

  
239
-- | Single-node relocation cost
269
-- | Single-node relocation cost.
240 270
nodeDelta :: Ndx -> Ndx -> Ndx -> Int
241 271
nodeDelta i p s =
242 272
    if i == p || i == s then
......
244 274
    else
245 275
        1
246 276

  
247
{-| Compute best solution.
248

  
249
    This function compares two solutions, choosing the minimum valid
250
    solution.
251
-}
277
-- | Compute best solution.
278
--
279
-- This function compares two solutions, choosing the minimum valid
280
-- solution.
252 281
compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
253 282
compareSolutions a b = case (a, b) of
254 283
  (Nothing, x) -> x
255 284
  (x, Nothing) -> x
256 285
  (x, y) -> min x y
257 286

  
258
-- | Compute best table. Note that the ordering of the arguments is important.
259
compareTables :: Table -> Table -> Table
260
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
261
    if a_cv > b_cv then b else a
262

  
263 287
-- | Check if a given delta is worse then an existing solution.
264 288
tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
265 289
tooHighDelta sol new_delta max_delta =
......
330 354
                ) accu_p nodes
331 355
    ) prev_sol nodes
332 356

  
333
-- | Apply a move
357
{-| Auxiliary function for solution computation.
358

  
359
We write this in an explicit recursive fashion in order to control
360
early-abort in case we have met the min delta. We can't use foldr
361
instead of explicit recursion since we need the accumulator for the
362
abort decision.
363

  
364
-}
365
advanceSolution :: [Maybe Removal] -- ^ The removal to process
366
                -> Int             -- ^ Minimum delta parameter
367
                -> Int             -- ^ Maximum delta parameter
368
                -> Maybe Solution  -- ^ Current best solution
369
                -> Maybe Solution  -- ^ New best solution
370
advanceSolution [] _ _ sol = sol
371
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
372
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
373
    let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
374
        new_delta = solutionDelta $! new_sol
375
    in
376
      if new_delta >= 0 && new_delta <= min_d then
377
          new_sol
378
      else
379
          advanceSolution xs min_d max_d new_sol
380

  
381
-- | Computes the placement solution.
382
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
383
                     -> Int             -- ^ Minimum delta parameter
384
                     -> Int             -- ^ Maximum delta parameter
385
                     -> Maybe Solution  -- ^ The best solution found
386
solutionFromRemovals removals min_delta max_delta =
387
    advanceSolution removals min_delta max_delta Nothing
388

  
389
{-| Computes the solution at the given depth.
390

  
391
This is a wrapper over both computeRemovals and
392
solutionFromRemovals. In case we have no solution, we return Nothing.
393

  
394
-}
395
computeSolution :: Node.List        -- ^ The original node data
396
                -> [Instance.Instance] -- ^ The list of /bad/ instances
397
                -> Int             -- ^ The /depth/ of removals
398
                -> Int             -- ^ Maximum number of removals to process
399
                -> Int             -- ^ Minimum delta parameter
400
                -> Int             -- ^ Maximum delta parameter
401
                -> Maybe Solution  -- ^ The best solution found (or Nothing)
402
computeSolution nl bad_instances depth max_removals min_delta max_delta =
403
  let
404
      removals = computeRemovals nl bad_instances depth
405
      removals' = capRemovals removals max_removals
406
  in
407
    solutionFromRemovals removals' min_delta max_delta
408

  
409
-- * hbal functions
410

  
411
-- | Compute best table. Note that the ordering of the arguments is important.
412
compareTables :: Table -> Table -> Table
413
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
414
    if a_cv > b_cv then b else a
415

  
416
-- | Applies an instance move to a given node list and instance.
334 417
applyMove :: Node.List -> Instance.Instance
335 418
          -> IMove -> (Maybe Node.List, Instance.Instance, Ndx, Ndx)
336 419
-- Failover (f)
......
407 490
                 Container.addTwo old_sdx new_p old_pdx int_p nl
408 491
    in (new_nl, Instance.setBoth inst old_sdx new_sdx, old_sdx, new_sdx)
409 492

  
493
-- | Tries to allocate an instance on one given node.
410 494
allocateOnSingle :: Node.List -> Instance.Instance -> Node.Node
411 495
                 -> (Maybe Node.List, Instance.Instance)
412 496
allocateOnSingle nl inst p =
......
415 499
                 return $ Container.add new_pdx new_p nl
416 500
    in (new_nl, Instance.setBoth inst new_pdx Node.noSecondary)
417 501

  
502
-- | Tries to allocate an instance on a given pair of nodes.
418 503
allocateOnPair :: Node.List -> Instance.Instance -> Node.Node -> Node.Node
419 504
               -> (Maybe Node.List, Instance.Instance)
420 505
allocateOnPair nl inst tgt_p tgt_s =
......
426 511
          return $ Container.addTwo new_pdx new_p new_sdx new_s nl
427 512
    in (new_nl, Instance.setBoth inst new_pdx new_sdx)
428 513

  
514
-- | Tries to perform an instance move and returns the best table
515
-- between the original one and the new one.
429 516
checkSingleStep :: Table -- ^ The original table
430 517
                -> Instance.Instance -- ^ The instance to move
431 518
                -> Table -- ^ The current best table
......
502 589
      else
503 590
          best_tbl
504 591

  
505
{- | Auxiliary function for solution computation.
506

  
507
We write this in an explicit recursive fashion in order to control
508
early-abort in case we have met the min delta. We can't use foldr
509
instead of explicit recursion since we need the accumulator for the
510
abort decision.
511

  
512
-}
513
advanceSolution :: [Maybe Removal] -- ^ The removal to process
514
                -> Int             -- ^ Minimum delta parameter
515
                -> Int             -- ^ Maximum delta parameter
516
                -> Maybe Solution  -- ^ Current best solution
517
                -> Maybe Solution  -- ^ New best solution
518
advanceSolution [] _ _ sol = sol
519
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
520
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
521
    let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
522
        new_delta = solutionDelta $! new_sol
523
    in
524
      if new_delta >= 0 && new_delta <= min_d then
525
          new_sol
526
      else
527
          advanceSolution xs min_d max_d new_sol
528

  
529
-- | Computes the placement solution.
530
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
531
                     -> Int             -- ^ Minimum delta parameter
532
                     -> Int             -- ^ Maximum delta parameter
533
                     -> Maybe Solution  -- ^ The best solution found
534
solutionFromRemovals removals min_delta max_delta =
535
    advanceSolution removals min_delta max_delta Nothing
536

  
537
{- | Computes the solution at the given depth.
538

  
539
This is a wrapper over both computeRemovals and
540
solutionFromRemovals. In case we have no solution, we return Nothing.
541 592

  
542
-}
543
computeSolution :: Node.List        -- ^ The original node data
544
                -> [Instance.Instance] -- ^ The list of /bad/ instances
545
                -> Int             -- ^ The /depth/ of removals
546
                -> Int             -- ^ Maximum number of removals to process
547
                -> Int             -- ^ Minimum delta parameter
548
                -> Int             -- ^ Maximum delta parameter
549
                -> Maybe Solution  -- ^ The best solution found (or Nothing)
550
computeSolution nl bad_instances depth max_removals min_delta max_delta =
551
  let
552
      removals = computeRemovals nl bad_instances depth
553
      removals' = capRemovals removals max_removals
554
  in
555
    solutionFromRemovals removals' min_delta max_delta
556

  
557
-- Solution display functions (pure)
593
-- * Formatting functions
558 594

  
559 595
-- | Given the original and final nodes, computes the relocation description.
560 596
computeMoves :: String -- ^ The instance name
......
600 636
                      printf "migrate -f %s" i,
601 637
                      printf "replace-disks -n %s %s" d i])
602 638

  
603
{-| Converts a placement to string format -}
604
printSolutionLine :: Node.List
605
                  -> Instance.List
606
                  -> Int
607
                  -> Int
608
                  -> Placement
609
                  -> Int
639
-- | Converts a placement to string format.
640
printSolutionLine :: Node.List     -- ^ The node list
641
                  -> Instance.List -- ^ The instance list
642
                  -> Int           -- ^ Maximum node name length
643
                  -> Int           -- ^ Maximum instance name length
644
                  -> Placement     -- ^ The current placement
645
                  -> Int           -- ^ The index of the placement in
646
                                   -- the solution
610 647
                  -> (String, [String])
611 648
printSolutionLine nl il nmlen imlen plc pos =
612 649
    let
......
627 664
       pmlen nstr c moves,
628 665
       cmds)
629 666

  
667
-- | Given a list of commands, prefix them with @gnt-instance@ and
668
-- also beautify the display a little.
630 669
formatCmds :: [[String]] -> String
631 670
formatCmds cmd_strs =
632 671
    unlines $
......
636 675
        (map ("gnt-instance " ++) b)) $
637 676
        zip [1..] cmd_strs
638 677

  
639
{-| Converts a solution to string format -}
678
-- | Converts a solution to string format.
640 679
printSolution :: Node.List
641 680
              -> Instance.List
642 681
              -> [Placement]
......
663 702
                 "pri" "sec" "p_fmem" "p_fdsk"
664 703
    in unlines $ (header:map helper snl)
665 704

  
666
-- | Compute the mem and disk covariance.
667
compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double)
668
compDetailedCV nl =
669
    let
670
        all_nodes = Container.elems nl
671
        (offline, nodes) = partition Node.offline all_nodes
672
        mem_l = map Node.p_mem nodes
673
        dsk_l = map Node.p_dsk nodes
674
        mem_cv = varianceCoeff mem_l
675
        dsk_cv = varianceCoeff dsk_l
676
        n1_l = length $ filter Node.failN1 nodes
677
        n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
678
        res_l = map Node.p_rem nodes
679
        res_cv = varianceCoeff res_l
680
        offline_inst = sum . map (\n -> (length . Node.plist $ n) +
681
                                        (length . Node.slist $ n)) $ offline
682
        online_inst = sum . map (\n -> (length . Node.plist $ n) +
683
                                       (length . Node.slist $ n)) $ nodes
684
        off_score = (fromIntegral offline_inst) /
685
                    (fromIntegral $ online_inst + offline_inst)
686
    in (mem_cv, dsk_cv, n1_score, res_cv, off_score)
687

  
688
-- | Compute the 'total' variance.
689
compCV :: Node.List -> Double
690
compCV nl =
691
    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
692
    in mem_cv + dsk_cv + n1_score + res_cv + off_score
693

  
705
-- | Shows statistics for a given node list.
694 706
printStats :: Node.List -> String
695 707
printStats nl =
696 708
    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
b/Ganeti/HTools/Container.hs
86 86
addTwo :: Key -> a -> Key -> a -> Container a -> Container a
87 87
addTwo k1 v1 k2 v2 c = add k1 v1 $ add k2 v2 c
88 88

  
89
-- | Compute the name of an element in a container
89
-- | Compute the name of an element in a container.
90 90
nameOf :: (T.Element a) => Container a -> Key -> String
91 91
nameOf c k = T.nameOf $ find k c
92 92

  
93
-- | Compute the maximum name length in an Element Container
93
-- | Compute the maximum name length in an Element Container.
94 94
maxNameLen :: (T.Element a) => Container a -> Int
95 95
maxNameLen = maximum . map (length . T.nameOf) . elems
96 96

  
97
-- | Find an element by name in a Container; this is a very slow function
97
-- | Find an element by name in a Container; this is a very slow function.
98 98
findByName :: (T.Element a, Monad m) =>
99 99
              Container a -> String -> m Key
100 100
findByName c n =
b/Ganeti/HTools/IAlloc.hs
11 11
    ) where
12 12

  
13 13
import Data.Either ()
14
--import Data.Maybe
15 14
import Control.Monad
16 15
import Text.JSON (JSObject, JSValue(JSBool, JSString, JSArray),
17 16
                  makeObj, encodeStrict, decodeStrict,
18 17
                  fromJSObject, toJSString)
19
--import Text.Printf (printf)
20 18
import qualified Ganeti.HTools.Container as Container
21 19
import qualified Ganeti.HTools.Node as Node
22 20
import qualified Ganeti.HTools.Instance as Instance
......
24 22
import Ganeti.HTools.Utils
25 23
import Ganeti.HTools.Types
26 24

  
25
-- | The request type.
27 26
data RqType
28
    = Allocate Instance.Instance Int
29
    | Relocate Idx Int [Ndx]
27
    = Allocate Instance.Instance Int -- ^ A new instance allocation
28
    | Relocate Idx Int [Ndx]         -- ^ Move an instance to a new
29
                                     -- secondary node
30 30
    deriving (Show)
31 31

  
32
-- | A complete request, as received from Ganeti.
32 33
data Request = Request RqType Node.List Instance.List String
33 34
    deriving (Show)
34 35

  
36
-- | Parse the basic specifications of an instance.
37
--
38
-- Instances in the cluster instance list and the instance in an
39
-- 'Allocate' request share some common properties, which are read by
40
-- this function.
35 41
parseBaseInstance :: String
36 42
                  -> JSObject JSValue
37 43
                  -> Result (String, Instance.Instance)
......
48 54
  let running = "running"
49 55
  return $ (n, Instance.create n mem disk running 0 0)
50 56

  
51
parseInstance :: NameAssoc
52
              -> String
53
              -> JSObject JSValue
57
-- | Parses an instance as found in the cluster instance list.
58
parseInstance :: NameAssoc        -- ^ The node name-to-index association list
59
              -> String           -- ^ The name of the instance
60
              -> JSObject JSValue -- ^ The JSON object
54 61
              -> Result (String, Instance.Instance)
55 62
parseInstance ktn n a = do
56 63
    base <- parseBaseInstance n a
......
62 69
             else (readEitherString $ head snodes) >>= lookupNode ktn n)
63 70
    return (n, Instance.setBoth (snd base) pidx sidx)
64 71

  
65
parseNode :: String -> JSObject JSValue -> Result (String, Node.Node)
72
-- | Parses a node as found in the cluster node list.
73
parseNode :: String           -- ^ The node's name
74
          -> JSObject JSValue -- ^ The JSON object
75
          -> Result (String, Node.Node)
66 76
parseNode n a = do
67 77
    let name = n
68 78
    offline <- fromObj "offline" a
......
79 89
                        dtotal dfree (offline || drained))
80 90
    return (name, node)
81 91

  
82
parseData :: String -> Result Request
92
-- | Top-level parser.
93
parseData :: String         -- ^ The JSON message as received from Ganeti
94
          -> Result Request -- ^ A (possible valid) request
83 95
parseData body = do
84 96
  decoded <- fromJResult $ decodeStrict body
85 97
  let obj = decoded
......
116 128
        other -> fail $ ("Invalid request type '" ++ other ++ "'")
117 129
  return $ Request rqtype map_n map_i csf
118 130

  
119
formatResponse :: Bool -> String -> [String] -> String
131
-- | Formats the response into a valid IAllocator response message.
132
formatResponse :: Bool     -- ^ Whether the request was successful
133
               -> String   -- ^ Information text
134
               -> [String] -- ^ The list of chosen nodes
135
               -> String   -- ^ The JSON-formatted message
120 136
formatResponse success info nodes =
121 137
    let
122 138
        e_success = ("success", JSBool success)
b/Ganeti/HTools/Instance.hs
9 9
import qualified Ganeti.HTools.Types as T
10 10
import qualified Ganeti.HTools.Container as Container
11 11

  
12
data Instance = Instance { name :: String   -- ^ the instance name
13
                         , mem :: Int       -- ^ memory of the instance
14
                         , dsk :: Int       -- ^ disk size of instance
15
                         , running :: Bool  -- ^ whether the instance
12
-- * Type declarations
13

  
14
-- | The instance type
15
data Instance = Instance { name :: String   -- ^ The instance name
16
                         , mem :: Int       -- ^ Memory of the instance
17
                         , dsk :: Int       -- ^ Disk size of instance
18
                         , running :: Bool  -- ^ Whether the instance
16 19
                                            -- is running
17
                         , run_st :: String -- ^ original (text) run status
18
                         , pnode :: T.Ndx   -- ^ original primary node
19
                         , snode :: T.Ndx   -- ^ original secondary node
20
                         , idx :: T.Idx     -- ^ internal index for
20
                         , run_st :: String -- ^ Original (text) run status
21
                         , pnode :: T.Ndx   -- ^ Original primary node
22
                         , snode :: T.Ndx   -- ^ Original secondary node
23
                         , idx :: T.Idx     -- ^ Internal index for
21 24
                                            -- book-keeping
22 25
                         } deriving (Show)
23 26

  
......
27 30
    setName = setName
28 31
    setIdx  = setIdx
29 32

  
30
-- | A simple name for the int, instance association list
33
-- | A simple name for the int, instance association list.
31 34
type AssocList = [(T.Idx, Instance)]
32 35

  
33
-- | A simple name for an instance map
36
-- | A simple name for an instance map.
34 37
type List = Container.Container Instance
35 38

  
39
-- * Initialization
40

  
41
-- | Create an instance.
42
--
43
-- Some parameters are not initialized by function, and must be set
44
-- later (via 'setIdx' for example).
36 45
create :: String -> Int -> Int -> String -> T.Ndx -> T.Ndx -> Instance
37 46
create name_init mem_init dsk_init run_init pn sn =
38 47
    Instance {
......
49 58
          idx = -1
50 59
        }
51 60

  
61
-- | Changes the index.
62
--
63
-- This is used only during the building of the data structures.
64
setIdx :: Instance  -- ^ the original instance
65
        -> T.Idx    -- ^ new index
66
        -> Instance -- ^ the modified instance
67
setIdx t i = t { idx = i }
68

  
69
-- | Changes the name.
70
--
71
-- This is used only during the building of the data structures.
72
setName :: Instance -- ^ The original instance
73
        -> String   -- ^ New name
74
        -> Instance
75
setName t s = t { name = s }
76

  
77
-- * Update functions
78

  
52 79
-- | Changes the primary node of the instance.
53 80
setPri :: Instance  -- ^ the original instance
54 81
        -> T.Ndx    -- ^ the new primary node
......
67 94
         -> T.Ndx    -- ^ new secondary node index
68 95
         -> Instance -- ^ the modified instance
69 96
setBoth t p s = t { pnode = p, snode = s }
70

  
71
-- | Changes the index.
72
-- This is used only during the building of the data structures.
73
setIdx :: Instance  -- ^ the original instance
74
        -> T.Idx    -- ^ new index
75
        -> Instance -- ^ the modified instance
76
setIdx t i = t { idx = i }
77

  
78
-- | Changes the name
79
-- This is used only during the building of the data structures.
80
setName t s = t { name = s }
b/Ganeti/HTools/Loader.hs
23 23

  
24 24
import Ganeti.HTools.Types
25 25

  
26
-- | Lookups a node into an assoc list
26
-- | Lookups a node into an assoc list.
27 27
lookupNode :: (Monad m) => [(String, Ndx)] -> String -> String -> m Ndx
28 28
lookupNode ktn inst node =
29 29
    case lookup node ktn of
30 30
      Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
31 31
      Just idx -> return idx
32 32

  
33
-- | Lookups an instance into an assoc list
33
-- | Lookups an instance into an assoc list.
34 34
lookupInstance :: (Monad m) => [(String, Idx)] -> String -> m Idx
35 35
lookupInstance kti inst =
36 36
    case lookup inst kti of
37 37
      Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
38 38
      Just idx -> return idx
39 39

  
40
-- | Given a list of elements (and their names), assign indices to them
40
-- | Given a list of elements (and their names), assign indices to them.
41 41
assignIndices :: (Element a) =>
42 42
                 [(String, a)]
43 43
              -> (NameAssoc, [(Int, a)])
......
45 45
    unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
46 46
          . zip [0..]
47 47

  
48
-- | For each instance, add its index to its primary and secondary nodes
48
-- | For each instance, add its index to its primary and secondary nodes.
49 49
fixNodes :: [(Ndx, Node.Node)]
50 50
         -> [(Idx, Instance.Instance)]
51 51
         -> [(Ndx, Node.Node)]
......
72 72
           ) nl il
73 73

  
74 74
-- | Compute the longest common suffix of a list of strings that
75
-- | starts with a dot
75
-- | starts with a dot.
76 76
longestDomain :: [String] -> String
77 77
longestDomain [] = ""
78 78
longestDomain (x:xs) =
......
81 81
                              else accu)
82 82
      "" $ filter (isPrefixOf ".") (tails x)
83 83

  
84
-- | Remove tail suffix from a string
84
-- | Remove tail suffix from a string.
85 85
stripSuffix :: Int -> String -> String
86 86
stripSuffix sflen name = take ((length name) - sflen) name
87 87

  
88
{-| Initializer function that loads the data from a node and list file
89
    and massages it into the correct format. -}
88
-- | Initializer function that loads the data from a node and instance
89
-- list and massages it into the correct format.
90 90
mergeData :: (Node.AssocList,
91 91
              Instance.AssocList) -- ^ Data from either Text.loadData
92 92
                                  -- or Rapi.loadData
......
105 105
      sil = Container.map (\i -> setName i (stripSuffix csl $ nameOf i)) il3
106 106
  return (snl, sil, common_suffix)
107 107

  
108
-- | Check cluster data for consistency
108
-- | Checks the cluster data for consistency.
109 109
checkData :: Node.List -> Instance.List
110 110
          -> ([String], Node.List)
111 111
checkData nl il =
b/Ganeti/HTools/Node.hs
18 18
    , setOffline
19 19
    , setXmem
20 20
    , setFmem
21
    , setPri
22
    , setSec
21 23
    -- * Instance (re)location
22 24
    , removePri
23 25
    , removeSec
24 26
    , addPri
25 27
    , addSec
26
    , setPri
27
    , setSec
28 28
    -- * Formatting
29 29
    , list
30 30
    -- * Misc stuff
......
41 41

  
42 42
import qualified Ganeti.HTools.Types as T
43 43

  
44
data Node = Node { name  :: String -- ^ the node name
45
                 , t_mem :: Double -- ^ total memory (MiB)
46
                 , n_mem :: Int    -- ^ node memory (MiB)
47
                 , f_mem :: Int    -- ^ free memory (MiB)
48
                 , x_mem :: Int    -- ^ unaccounted memory (MiB)
49
                 , t_dsk :: Double -- ^ total disk space (MiB)
50
                 , f_dsk :: Int    -- ^ free disk space (MiB)
51
                 , plist :: [T.Idx]-- ^ list of primary instance indices
52
                 , slist :: [T.Idx]-- ^ list of secondary instance indices
53
                 , idx :: T.Ndx    -- ^ internal index for book-keeping
54
                 , peers :: PeerMap.PeerMap -- ^ pnode to instance mapping
55
                 , failN1:: Bool   -- ^ whether the node has failed n1
56
                 , r_mem :: Int    -- ^ maximum memory needed for
44
-- * Type declarations
45

  
46
-- | The node type.
47
data Node = Node { name  :: String -- ^ The node name
48
                 , t_mem :: Double -- ^ Total memory (MiB)
49
                 , n_mem :: Int    -- ^ Node memory (MiB)
50
                 , f_mem :: Int    -- ^ Free memory (MiB)
51
                 , x_mem :: Int    -- ^ Unaccounted memory (MiB)
52
                 , t_dsk :: Double -- ^ Total disk space (MiB)
53
                 , f_dsk :: Int    -- ^ Free disk space (MiB)
54
                 , plist :: [T.Idx]-- ^ List of primary instance indices
55
                 , slist :: [T.Idx]-- ^ List of secondary instance indices
56
                 , idx :: T.Ndx    -- ^ Internal index for book-keeping
57
                 , peers :: PeerMap.PeerMap -- ^ Pnode to instance mapping
58
                 , failN1:: Bool   -- ^ Whether the node has failed n1
59
                 , r_mem :: Int    -- ^ Maximum memory needed for
57 60
                                   -- failover by primaries of this node
58
                 , p_mem :: Double -- ^ percent of free memory
59
                 , p_dsk :: Double -- ^ percent of free disk
60
                 , p_rem :: Double -- ^ percent of reserved memory
61
                 , offline :: Bool -- ^ whether the node should not be used
61
                 , p_mem :: Double -- ^ Percent of free memory
62
                 , p_dsk :: Double -- ^ Percent of free disk
63
                 , p_rem :: Double -- ^ Percent of reserved memory
64
                 , offline :: Bool -- ^ Whether the node should not be used
62 65
                                   -- for allocations and skipped from
63 66
                                   -- score computations
64 67
  } deriving (Show)
......
69 72
    setName = setName
70 73
    setIdx = setIdx
71 74

  
72
-- | A simple name for the int, node association list
75
-- | A simple name for the int, node association list.
73 76
type AssocList = [(T.Ndx, Node)]
74 77

  
75
-- | A simple name for a node map
78
-- | A simple name for a node map.
76 79
type List = Container.Container Node
77 80

  
78
-- | Constant node index for a non-moveable instance
81
-- | Constant node index for a non-moveable instance.
79 82
noSecondary :: T.Ndx
80 83
noSecondary = -1
81 84

  
82
{- | Create a new node.
83

  
84
The index and the peers maps are empty, and will be need to be update
85
later via the 'setIdx' and 'buildPeers' functions.
85
-- * Initialization functions
86 86

  
87
-}
87
-- | Create a new node.
88
--
89
-- The index and the peers maps are empty, and will be need to be
90
-- update later via the 'setIdx' and 'buildPeers' functions.
88 91
create :: String -> Double -> Int -> Int -> Double -> Int -> Bool -> Node
89 92
create name_init mem_t_init mem_n_init mem_f_init
90 93
       dsk_t_init dsk_f_init offline_init =
......
110 113
    }
111 114

  
112 115
-- | Changes the index.
116
--
113 117
-- This is used only during the building of the data structures.
114 118
setIdx :: Node -> T.Ndx -> Node
115 119
setIdx t i = t {idx = i}
116 120

  
117
-- | Changes the name
121
-- | Changes the name.
122
--
118 123
-- This is used only during the building of the data structures.
124
setName :: Node -> String -> Node
119 125
setName t s = t {name = s}
120 126

  
121
-- | Sets the offline attribute
127
-- | Sets the offline attribute.
122 128
setOffline :: Node -> Bool -> Node
123 129
setOffline t val = t { offline = val }
124 130

  
125
-- | Sets the unnaccounted memory
131
-- | Sets the unnaccounted memory.
126 132
setXmem :: Node -> Int -> Node
127 133
setXmem t val = t { x_mem = val }
128 134

  
129
-- | Sets the free memory
130
setFmem :: Node -> Int -> Node
131
setFmem t new_mem =
132
    let new_n1 = computeFailN1 (r_mem t) new_mem (f_dsk t)
133
        new_mp = (fromIntegral new_mem) / (t_mem t)
134
    in
135
      t { f_mem = new_mem, failN1 = new_n1, p_mem = new_mp }
136

  
137
-- | Given the rmem, free memory and disk, computes the failn1 status.
138
computeFailN1 :: Int -> Int -> Int -> Bool
139
computeFailN1 new_rmem new_mem new_dsk =
140
    new_mem <= new_rmem || new_dsk <= 0
141

  
142
-- | Given the new free memory and disk, fail if any of them is below zero.
143
failHealth :: Int -> Int -> Bool
144
failHealth new_mem new_dsk = new_mem <= 0 || new_dsk <= 0
145

  
146 135
-- | Computes the maximum reserved memory for peers from a peer map.
147 136
computeMaxRes :: PeerMap.PeerMap -> PeerMap.Elem
148 137
computeMaxRes new_peers = PeerMap.maxElem new_peers
......
160 149
        new_prem = (fromIntegral new_rmem) / (t_mem t)
161 150
    in t {peers=pmap, failN1 = new_failN1, r_mem = new_rmem, p_rem = new_prem}
162 151

  
152
-- | Assigns an instance to a node as primary without other updates.
153
setPri :: Node -> T.Idx -> Node
154
setPri t idx = t { plist = idx:(plist t) }
155

  
156
-- | Assigns an instance to a node as secondary without other updates.
157
setSec :: Node -> T.Idx -> Node
158
setSec t idx = t { slist = idx:(slist t) }
159

  
160
-- * Update functions
161

  
162
-- | Sets the free memory.
163
setFmem :: Node -> Int -> Node
164
setFmem t new_mem =
165
    let new_n1 = computeFailN1 (r_mem t) new_mem (f_dsk t)
166
        new_mp = (fromIntegral new_mem) / (t_mem t)
167
    in
168
      t { f_mem = new_mem, failN1 = new_n1, p_mem = new_mp }
169

  
170
-- | Given the rmem, free memory and disk, computes the failn1 status.
171
computeFailN1 :: Int -> Int -> Int -> Bool
172
computeFailN1 new_rmem new_mem new_dsk =
173
    new_mem <= new_rmem || new_dsk <= 0
174

  
175
-- | Given the new free memory and disk, fail if any of them is below zero.
176
failHealth :: Int -> Int -> Bool
177
failHealth new_mem new_dsk = new_mem <= 0 || new_dsk <= 0
178

  
163 179
-- | Removes a primary instance.
164 180
removePri :: Node -> Instance.Instance -> Node
165 181
removePri t inst =
......
236 252
                r_mem = new_rmem, p_dsk = new_dp,
237 253
                p_rem = new_prem}
238 254

  
239
-- | Add a primary instance to a node without other updates
240
setPri :: Node -> T.Idx -> Node
241
setPri t idx = t { plist = idx:(plist t) }
242

  
243
-- | Add a secondary instance to a node without other updates
244
setSec :: Node -> T.Idx -> Node
245
setSec t idx = t { slist = idx:(slist t) }
255
-- * Display functions
246 256

  
247 257
-- | String converter for the node list functionality.
248 258
list :: Int -> Node -> String
b/Ganeti/HTools/PeerMap.hs
30 30
type Elem = Int
31 31
type PeerMap = [(Key, Elem)]
32 32

  
33
-- | Create a new empty map
33
-- * Initialization functions
34

  
35
-- | Create a new empty map.
34 36
empty :: PeerMap
35 37
empty = []
36 38

  
37
-- | Our reverse-compare function
39
-- | Our reverse-compare function.
38 40
pmCompare :: (Key, Elem) -> (Key, Elem) -> Ordering
39 41
pmCompare a b = (compare `on` snd) b a
40 42

  
41
-- | Add or update (via a custom function) an element
43
-- | Add or update (via a custom function) an element.
42 44
addWith :: (Elem -> Elem -> Elem) -> Key -> Elem -> PeerMap -> PeerMap
43 45
addWith fn k v lst =
44 46
    let r = lookup k lst
......
56 58
      [] -> empty
57 59
      (k, v):xs -> addWith fn k v $ accumArray fn xs
58 60

  
61
-- * Basic operations
62

  
63
-- | Returns either the value for a key or zero if not found
59 64
find :: Key -> PeerMap -> Elem
60 65
find k c = fromMaybe 0 $ lookup k c
61 66

  
67
-- | Add an element to a peermap, overwriting the previous value
62 68
add :: Key -> Elem -> PeerMap -> PeerMap
63 69
add k v c = addWith (flip const) k v c
64 70

  
71
-- | Remove an element from a peermap
65 72
remove :: Key -> PeerMap -> PeerMap
66 73
remove k c = case c of
67 74
               [] -> []
68 75
               (x@(x', _)):xs -> if k == x' then xs
69 76
                            else x:(remove k xs)
70 77

  
71
-- | Find the maximum element. Since this is a sorted list, we just
72
-- get the first one
78
-- | Find the maximum element.
79
--
80
-- Since this is a sorted list, we just get the value at the head of
81
-- the list, or zero for a null list
73 82
maxElem :: PeerMap -> Elem
74 83
maxElem c = if null c then 0 else snd . head $ c
b/Ganeti/HTools/Rapi.hs
21 21
import qualified Ganeti.HTools.Node as Node
22 22
import qualified Ganeti.HTools.Instance as Instance
23 23

  
24
-- | Read an URL via curl and return the body if successful
24
-- | Read an URL via curl and return the body if successful.
25 25
getUrl :: (Monad m) => String -> IO (m String)
26 26
getUrl url = do
27 27
  (code, body) <- curlGetString url [CurlSSLVerifyPeer False,
......
31 31
            _ -> fail $ printf "Curl error for '%s', error %s"
32 32
                 url (show code))
33 33

  
34
-- | Append the default port if not passed in
34
-- | Append the default port if not passed in.
35 35
formatHost :: String -> String
36 36
formatHost master =
37 37
    if elem ':' master then  master
38 38
    else "https://" ++ master ++ ":5080"
39 39

  
40
-- | Parse a instance list in JSON format.
40 41
getInstances :: NameAssoc
41 42
             -> String
42 43
             -> Result [(String, Instance.Instance)]
......
45 46
  ilist <- mapM (parseInstance ktn) arr
46 47
  return ilist
47 48

  
49
-- | Parse a node list in JSON format.
48 50
getNodes :: String -> Result [(String, Node.Node)]
49 51
getNodes body = do
50 52
  arr <- loadJSArray body
51 53
  nlist <- mapM parseNode arr
52 54
  return nlist
53 55

  
56
-- | Construct an instance from a JSON object.
54 57
parseInstance :: [(String, Ndx)]
55 58
              -> JSObject JSValue
56 59
              -> Result (String, Instance.Instance)
......
66 69
  let inst = Instance.create name mem disk running pnode snode
67 70
  return (name, inst)
68 71

  
72
-- | Construct a node from a JSON object.
69 73
parseNode :: JSObject JSValue -> Result (String, Node.Node)
70 74
parseNode a = do
71 75
    name <- fromObj "name" a
......
83 87
                        dtotal dfree (offline || drained))
84 88
    return (name, node)
85 89

  
90
-- | Builds the cluster data from an URL.
86 91
loadData :: String -- ^ Cluster or URL to use as source
87 92
         -> IO (Result (Node.AssocList, Instance.AssocList))
88 93
loadData master = do -- IO monad
b/Ganeti/HTools/Text.hs
16 16
import qualified Ganeti.HTools.Node as Node
17 17
import qualified Ganeti.HTools.Instance as Instance
18 18

  
19
-- | Safe 'read' function returning data encapsulated in a Result
19
-- | Safe 'read' function returning data encapsulated in a Result.
20 20
tryRead :: (Monad m, Read a) => String -> String -> m a
21 21
tryRead name s =
22 22
    let sols = readsPrec 0 s
......
26 26
                      ++ s ++ "': '" ++ e ++ "'"
27 27
         _ -> fail $ name ++ ": cannot parse string '" ++ s ++ "'"
28 28

  
29
-- | Load a node from a field list
29
-- | Load a node from a field list.
30 30
loadNode :: (Monad m) => [String] -> m (String, Node.Node)
31 31
loadNode (name:tm:nm:fm:td:fd:fo:[]) = do
32 32
  new_node <-
......
42 42
  return (name, new_node)
43 43
loadNode s = fail $ "Invalid/incomplete node data: '" ++ (show s) ++ "'"
44 44

  
45
-- | Load an instance from a field list
45
-- | Load an instance from a field list.
46 46
loadInst :: (Monad m) =>
47 47
            [(String, Ndx)] -> [String] -> m (String, Instance.Instance)
48 48
loadInst ktn (name:mem:dsk:status:pnode:snode:[]) = do
......
57 57
  return (name, newinst)
58 58
loadInst _ s = fail $ "Invalid/incomplete instance data: '" ++ (show s) ++ "'"
59 59

  
60
{- | Convert newline and delimiter-separated text.
61

  
62
This function converts a text in tabular format as generated by
63
@gnt-instance list@ and @gnt-node list@ to a list of objects using a
64
supplied conversion function.
65

  
66
-}
60
-- | Convert newline and delimiter-separated text.
61
--
62
-- This function converts a text in tabular format as generated by
63
-- @gnt-instance list@ and @gnt-node list@ to a list of objects using
64
-- a supplied conversion function.
67 65
loadTabular :: (Monad m, Element a) =>
68 66
               String -> ([String] -> m (String, a))
69 67
            -> m ([(String, Int)], [(Int, a)])
......
73 71
  kerows <- mapM convert_fn rows
74 72
  return $ assignIndices kerows
75 73

  
74
-- | Builds the cluster data from node\/instance files.
76 75
loadData :: String -- ^ Node data in string format
77 76
         -> String -- ^ Instance data in string format
78 77
         -> IO (Result (Node.AssocList, Instance.AssocList))
b/Ganeti/HTools/Types.hs
5 5
module Ganeti.HTools.Types
6 6
    where
7 7

  
8
-- | The instance index type
8
-- | The instance index type.
9 9
type Idx = Int
10 10

  
11
-- | The node index type
11
-- | The node index type.
12 12
type Ndx = Int
13 13

  
14
-- | The type used to hold name-to-idx mappings
14
-- | The type used to hold name-to-idx mappings.
15 15
type NameAssoc = [(String, Int)]
16 16

  
17 17
{-|
......
32 32
    return = Ok
33 33
    fail = Bad
34 34

  
35
-- | A generic class for items that have names and indices
35
-- | A generic class for items that have updateable names and indices.
36 36
class Element a where
37
    -- | Returns the name of the element
37 38
    nameOf  :: a -> String
39
    -- | Returns the index of the element
38 40
    idxOf   :: a -> Int
41
    -- | Updates the name of the element
39 42
    setName :: a -> String -> a
43
    -- | Updates the index of the element
40 44
    setIdx  :: a -> Int -> a
b/Ganeti/HTools/Utils.hs
26 26

  
27 27
import Debug.Trace
28 28

  
29
-- * Debug functions
30

  
29 31
-- | To be used only for debugging, breaks referential integrity.
30 32
debug :: Show a => a -> a
31 33
debug x = trace (show x) x
32 34

  
33

  
34
fromJResult :: Monad m => J.Result a -> m a
35
fromJResult (J.Error x) = fail x
36
fromJResult (J.Ok x) = return x
35
-- * Miscelaneous
37 36

  
38 37
-- | Comma-join a string list.
39 38
commaJoin :: [String] -> String
......
53 52
commaSplit :: String -> [String]
54 53
commaSplit = sepSplit ','
55 54

  
55
-- * Mathematical functions
56

  
56 57
-- Simple and slow statistical functions, please replace with better versions
57 58

  
58 59
-- | Mean value of a list.
......
72 73
varianceCoeff :: Floating a => [a] -> a
73 74
varianceCoeff lst = (stdDev lst) / (fromIntegral $ length lst)
74 75

  
75
-- | Get an Ok result or print the error and exit
76
-- | Get an Ok result or print the error and exit.
76 77
readData :: Result a -> IO a
77 78
readData nd =
78 79
    (case nd of
......
81 82
         exitWith $ ExitFailure 1
82 83
       Ok x -> return x)
83 84

  
85
-- * JSON-related functions
86

  
87
-- | Converts a JSON Result into a monadic value.
88
fromJResult :: Monad m => J.Result a -> m a
89
fromJResult (J.Error x) = fail x
90
fromJResult (J.Ok x) = return x
91

  
92
-- | Tries to read a string from a JSON value.
93
--
94
-- In case the value was not a string, we fail the read (in the
95
-- context of the current monad.
84 96
readEitherString :: (Monad m) => J.JSValue -> m String
85 97
readEitherString v =
86 98
    case v of
87 99
      J.JSString s -> return $ J.fromJSString s
88 100
      _ -> fail "Wrong JSON type"
89 101

  
102
-- | Converts a JSON message into an array of JSON objects.
90 103
loadJSArray :: (Monad m) => String -> m [J.JSObject J.JSValue]
91 104
loadJSArray s = fromJResult $ J.decodeStrict s
92 105

  
106
-- | Reads a the value of a key in a JSON object.
93 107
fromObj :: (J.JSON a, Monad m) => String -> J.JSObject J.JSValue -> m a
94 108
fromObj k o =
95 109
    case lookup k (J.fromJSObject o) of
96 110
      Nothing -> fail $ printf "key '%s' not found in %s" k (show o)
97 111
      Just val -> fromJResult $ J.readJSON val
98 112

  
113
-- | Converts a JSON value into a JSON object.
99 114
asJSObject :: (Monad m) => J.JSValue -> m (J.JSObject J.JSValue)
100 115
asJSObject (J.JSObject a) = return a
101 116
asJSObject _ = fail "not an object"
102 117

  
118
-- | Coneverts a list of JSON values into a list of JSON objects.
103 119
asObjectList :: (Monad m) => [J.JSValue] -> m [J.JSObject J.JSValue]
104 120
asObjectList = sequence . map asJSObject
b/Ganeti/HTools/Version.hs.in
5 5
      version
6 6
    ) where
7 7

  
8
-- | The version of the tree
8
-- | The version of the sources.
9
version :: String
9 10
version = "(htools) version %ver%"
b/Makefile
22 22
$(DOCS) : %.html : %
23 23
	rst2html $< $@
24 24

  
25
doc: $(DOCS)
26
	rm -rf $(HDDIR)
25
doc: $(DOCS) Ganeti/HTools/Version.hs
26
	rm -rf $(HDDIR)/*
27 27
	mkdir -p $(HDDIR)/Ganeti/HTools
28 28
	cp hscolour.css $(HDDIR)/Ganeti/HTools
29 29
	for file in $(HSRCS); do \

Also available in: Unified diff