1 {-| Cluster rolling maintenance helper.
7 Copyright (C) 2012 Google Inc.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 module Ganeti.HTools.Program.Hroller
36 import qualified Data.IntMap as IntMap
38 import qualified Ganeti.HTools.Container as Container
39 import qualified Ganeti.HTools.Node as Node
40 import qualified Ganeti.HTools.Group as Group
43 import Ganeti.HTools.CLI
44 import Ganeti.HTools.ExtLoader
45 import Ganeti.HTools.Graph
46 import Ganeti.HTools.Loader
49 -- | Options list and functions.
50 options :: IO [OptType]
66 -- | The list of arguments supported by the program.
67 arguments :: [ArgCompletion]
70 -- | Gather statistics for the coloring algorithms.
71 -- Returns a string with a summary on how each algorithm has performed,
72 -- in order of non-decreasing effectiveness, and whether it tied or lost
73 -- with the previous one.
74 getStats :: [(String, ColorVertMap)] -> String
75 getStats colorings = snd . foldr helper (0,"") $ algBySize colorings
76 where algostat (algo, cmap) = algo ++ ": " ++ size cmap ++ grpsizes cmap
77 size cmap = show (IntMap.size cmap) ++ " "
79 "(" ++ commaJoin (map (show.length) (IntMap.elems cmap)) ++ ")"
80 algBySize = sortBy (flip (comparing (IntMap.size.snd)))
81 helper :: (String, ColorVertMap) -> (Int, String) -> (Int, String)
82 helper el (0, _) = ((IntMap.size.snd) el, algostat el)
84 | old == elsize = (elsize, str ++ " TIE " ++ algostat el)
85 | otherwise = (elsize, str ++ " LOOSE " ++ algostat el)
86 where elsize = (IntMap.size.snd) el
88 -- | Filter the output list.
89 -- Only online nodes are shown, optionally belonging to a particular target
90 -- nodegroup. Output groups which are empty after being filtered are removed
92 filterOutput :: Maybe Group.Group -> [[Node.Node]] -> [[Node.Node]]
94 let onlineOnly = filter (not . Node.offline)
95 hasGroup grp node = Node.group node == Group.idx grp
96 byGroupOnly Nothing xs = xs
97 byGroupOnly (Just grp) xs = filter (hasGroup grp) xs
98 nonNullOnly = filter (not . null)
99 in nonNullOnly (map (onlineOnly . byGroupOnly g) l)
102 main :: Options -> [String] -> IO ()
104 unless (null args) $ exitErr "This program doesn't take any arguments."
106 let verbose = optVerbose opts
108 -- Load cluster data. The last two arguments, cluster tags and ipolicy, are
109 -- currently not used by this tool.
110 ini_cdata@(ClusterData gl fixed_nl ilf _ _) <- loadExternalData opts
112 nlf <- setNodeStatus opts fixed_nl
114 maybeSaveData (optSaveCluster opts) "original" "before hroller run" ini_cdata
116 -- Find the wanted node group, if any.
117 wantedGroup <- case optGroup opts of
118 Nothing -> return Nothing
119 Just name -> case Container.findByName gl name of
120 Nothing -> exitErr "Cannot find target group."
121 Just grp -> return (Just grp)
123 -- TODO: fail if instances are running (with option to warn only)
124 -- TODO: identify master node, and put it last
126 nodeGraph <- case Node.mkNodeGraph nlf ilf of
127 Nothing -> exitErr "Cannot create node graph"
130 when (verbose > 2) . putStrLn $ "Node Graph: " ++ show nodeGraph
132 let colorAlgorithms = [ ("LF", colorLF)
133 , ("Dsatur", colorDsatur)
134 , ("Dcolor", colorDcolor)
136 colorings = map (\(v,a) -> (v,(colorVertMap.a) nodeGraph)) colorAlgorithms
138 (snd . minimumBy (comparing (IntMap.size . snd))) colorings
139 idToNode = (`Container.find` nlf)
140 nodesRebootGroups = map (map idToNode) $ IntMap.elems smallestColoring
141 outputRebootGroups = filterOutput wantedGroup nodesRebootGroups
142 outputRebootNames = map (map Node.name) outputRebootGroups
144 when (verbose > 1) . putStrLn $ getStats colorings
146 unless (optNoHeaders opts) $
147 putStrLn "'Node Reboot Groups'"
148 mapM_ (putStrLn . commaJoin) outputRebootNames