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