1 {-| Generic data loader
3 This module holds the common code for parsing the input data after it
4 has been loaded from external sources.
10 Copyright (C) 2009, 2010, 2011 Google Inc.
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful, but
18 WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
29 module Ganeti.HTools.Loader
44 import qualified Data.Map as M
45 import Text.Printf (printf)
47 import qualified Ganeti.HTools.Container as Container
48 import qualified Ganeti.HTools.Instance as Instance
49 import qualified Ganeti.HTools.Node as Node
50 import qualified Ganeti.HTools.Group as Group
52 import Ganeti.HTools.Types
56 -- | The exclusion tag prefix
57 exTagsPrefix :: String
58 exTagsPrefix = "htools:iextags:"
62 {-| The iallocator request type.
64 This type denotes what request we got from Ganeti and also holds
65 request-specific fields.
69 = Allocate Instance.Instance Int -- ^ A new instance allocation
70 | Relocate Idx Int [Ndx] -- ^ Move an instance to a new
72 | Evacuate [Ndx] -- ^ Evacuate nodes
75 -- | A complete request, as received from Ganeti.
76 data Request = Request RqType ClusterData
79 -- | The cluster state.
80 data ClusterData = ClusterData
81 { cdGroups :: Group.List -- ^ The node group list
82 , cdNodes :: Node.List -- ^ The node list
83 , cdInstances :: Instance.List -- ^ The instance list
84 , cdTags :: [String] -- ^ The cluster tags
85 } deriving (Show, Read)
87 -- | An empty cluster.
88 emptyCluster :: ClusterData
89 emptyCluster = ClusterData Container.empty Container.empty Container.empty []
93 -- | Lookups a node into an assoc list.
94 lookupNode :: (Monad m) => NameAssoc -> String -> String -> m Ndx
95 lookupNode ktn inst node =
96 case M.lookup node ktn of
97 Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
98 Just idx -> return idx
100 -- | Lookups an instance into an assoc list.
101 lookupInstance :: (Monad m) => NameAssoc -> String -> m Idx
102 lookupInstance kti inst =
103 case M.lookup inst kti of
104 Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
105 Just idx -> return idx
107 -- | Lookups a group into an assoc list.
108 lookupGroup :: (Monad m) => NameAssoc -> String -> String -> m Gdx
109 lookupGroup ktg nname gname =
110 case M.lookup gname ktg of
111 Nothing -> fail $ "Unknown group '" ++ gname ++ "' for node " ++ nname
112 Just idx -> return idx
114 -- | Given a list of elements (and their names), assign indices to them.
115 assignIndices :: (Element a) =>
117 -> (NameAssoc, Container.Container a)
118 assignIndices nodes =
120 unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
122 in (M.fromList na, Container.fromList idx_node)
124 -- | For each instance, add its index to its primary and secondary nodes.
125 fixNodes :: Node.List
130 pdx = Instance.pNode inst
131 sdx = Instance.sNode inst
132 pold = Container.find pdx accu
133 pnew = Node.setPri pold inst
134 ac2 = Container.add pdx pnew accu
136 if sdx /= Node.noSecondary
137 then let sold = Container.find sdx accu
138 snew = Node.setSec sold inst
139 in Container.add sdx snew ac2
142 -- | Remove non-selected tags from the exclusion list
143 filterExTags :: [String] -> Instance.Instance -> Instance.Instance
144 filterExTags tl inst =
145 let old_tags = Instance.tags inst
146 new_tags = filter (\tag -> any (`isPrefixOf` tag) tl)
148 in inst { Instance.tags = new_tags }
150 -- | Update the movable attribute
151 updateMovable :: [String] -> Instance.Instance -> Instance.Instance
152 updateMovable exinst inst =
153 if Instance.sNode inst == Node.noSecondary ||
154 Instance.name inst `elem` exinst
155 then Instance.setMovable inst False
158 -- | Compute the longest common suffix of a list of strings that
159 -- | starts with a dot.
160 longestDomain :: [String] -> String
161 longestDomain [] = ""
162 longestDomain (x:xs) =
163 foldr (\ suffix accu -> if all (isSuffixOf suffix) xs
166 "" $ filter (isPrefixOf ".") (tails x)
168 -- | Extracts the exclusion tags from the cluster configuration
169 extractExTags :: [String] -> [String]
171 map (drop (length exTagsPrefix)) .
172 filter (isPrefixOf exTagsPrefix)
174 -- | Extracts the common suffix from node\/instance names
175 commonSuffix :: Node.List -> Instance.List -> String
177 let node_names = map Node.name $ Container.elems nl
178 inst_names = map Instance.name $ Container.elems il
179 in longestDomain (node_names ++ inst_names)
181 -- | Initializer function that loads the data from a node and instance
182 -- list and massages it into the correct format.
183 mergeData :: [(String, DynUtil)] -- ^ Instance utilisation data
184 -> [String] -- ^ Exclusion tags
185 -> [String] -- ^ Untouchable instances
186 -> ClusterData -- ^ Data from backends
187 -> Result ClusterData -- ^ Fixed cluster data
188 mergeData um extags exinsts cdata@(ClusterData _ nl il2 tags) =
189 let il = Container.elems il2
190 il3 = foldl' (\im (name, n_util) ->
191 case Container.findByName im name of
192 Nothing -> im -- skipping unknown instance
194 let new_i = inst { Instance.util = n_util }
195 in Container.add (Instance.idx inst) new_i im
197 allextags = extags ++ extractExTags tags
198 il4 = Container.map (filterExTags allextags .
199 updateMovable exinsts) il3
200 nl2 = foldl' fixNodes nl (Container.elems il4)
201 nl3 = Container.map (flip Node.buildPeers il4) nl2
202 node_names = map Node.name (Container.elems nl)
203 inst_names = map Instance.name il
204 common_suffix = longestDomain (node_names ++ inst_names)
205 snl = Container.map (computeAlias common_suffix) nl3
206 sil = Container.map (computeAlias common_suffix) il4
207 all_inst_names = concatMap allNames $ Container.elems sil
208 in if not $ all (`elem` all_inst_names) exinsts
209 then Bad $ "Some of the excluded instances are unknown: " ++
210 show (exinsts \\ all_inst_names)
211 else Ok cdata { cdNodes = snl, cdInstances = sil }
213 -- | Checks the cluster data for consistency.
214 checkData :: Node.List -> Instance.List
215 -> ([String], Node.List)
219 let nname = Node.name node
220 nilst = map (`Container.find` il) (Node.pList node)
221 dilst = filter (not . Instance.running) nilst
222 adj_mem = sum . map Instance.mem $ dilst
223 delta_mem = truncate (Node.tMem node)
228 delta_dsk = truncate (Node.tDsk node)
231 newn = Node.setFmem (Node.setXmem node delta_mem)
232 (Node.fMem node - adj_mem)
233 umsg1 = [printf "node %s is missing %d MB ram \
235 nname delta_mem (delta_dsk `div` 1024) |
236 delta_mem > 512 || delta_dsk > 1024]::[String]
237 in (msgs ++ umsg1, newn)
240 -- | Compute the amount of memory used by primary instances on a node.
241 nodeImem :: Node.Node -> Instance.List -> Int
243 let rfind = flip Container.find il
244 in sum . map (Instance.mem . rfind)
247 -- | Compute the amount of disk used by instances on a node (either primary
249 nodeIdsk :: Node.Node -> Instance.List -> Int
251 let rfind = flip Container.find il
252 in sum . map (Instance.dsk . rfind)
253 $ Node.pList node ++ Node.sList node