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
46 import qualified Data.Map as M
47 import Text.Printf (printf)
49 import qualified Ganeti.HTools.Container as Container
50 import qualified Ganeti.HTools.Instance as Instance
51 import qualified Ganeti.HTools.Node as Node
52 import qualified Ganeti.HTools.Group as Group
54 import Ganeti.HTools.Types
58 -- | The exclusion tag prefix.
59 exTagsPrefix :: String
60 exTagsPrefix = "htools:iextags:"
64 -- | The iallocator multi-evac group mode type.
65 data RelocMode = KeepGroup
70 {-| The iallocator request type.
72 This type denotes what request we got from Ganeti and also holds
73 request-specific fields.
77 = Allocate Instance.Instance Int -- ^ A new instance allocation
78 | Relocate Idx Int [Ndx] -- ^ Move an instance to a new
80 | Evacuate [Ndx] -- ^ Evacuate nodes
81 | MultiReloc [Idx] RelocMode -- ^ Multi-relocate mode
82 | NodeEvacuate [Idx] EvacMode -- ^ node-evacuate mode
85 -- | A complete request, as received from Ganeti.
86 data Request = Request RqType ClusterData
89 -- | The cluster state.
90 data ClusterData = ClusterData
91 { cdGroups :: Group.List -- ^ The node group list
92 , cdNodes :: Node.List -- ^ The node list
93 , cdInstances :: Instance.List -- ^ The instance list
94 , cdTags :: [String] -- ^ The cluster tags
95 } deriving (Show, Read)
97 -- | An empty cluster.
98 emptyCluster :: ClusterData
99 emptyCluster = ClusterData Container.empty Container.empty Container.empty []
103 -- | Lookups a node into an assoc list.
104 lookupNode :: (Monad m) => NameAssoc -> String -> String -> m Ndx
105 lookupNode ktn inst node =
106 case M.lookup node ktn of
107 Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
108 Just idx -> return idx
110 -- | Lookups an instance into an assoc list.
111 lookupInstance :: (Monad m) => NameAssoc -> String -> m Idx
112 lookupInstance kti inst =
113 case M.lookup inst kti of
114 Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
115 Just idx -> return idx
117 -- | Lookups a group into an assoc list.
118 lookupGroup :: (Monad m) => NameAssoc -> String -> String -> m Gdx
119 lookupGroup ktg nname gname =
120 case M.lookup gname ktg of
121 Nothing -> fail $ "Unknown group '" ++ gname ++ "' for node " ++ nname
122 Just idx -> return idx
124 -- | Given a list of elements (and their names), assign indices to them.
125 assignIndices :: (Element a) =>
127 -> (NameAssoc, Container.Container a)
128 assignIndices nodes =
130 unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
132 in (M.fromList na, Container.fromList idx_node)
134 -- | For each instance, add its index to its primary and secondary nodes.
135 fixNodes :: Node.List
140 pdx = Instance.pNode inst
141 sdx = Instance.sNode inst
142 pold = Container.find pdx accu
143 pnew = Node.setPri pold inst
144 ac2 = Container.add pdx pnew accu
146 if sdx /= Node.noSecondary
147 then let sold = Container.find sdx accu
148 snew = Node.setSec sold inst
149 in Container.add sdx snew ac2
152 -- | Remove non-selected tags from the exclusion list.
153 filterExTags :: [String] -> Instance.Instance -> Instance.Instance
154 filterExTags tl inst =
155 let old_tags = Instance.tags inst
156 new_tags = filter (\tag -> any (`isPrefixOf` tag) tl)
158 in inst { Instance.tags = new_tags }
160 -- | Update the movable attribute.
161 updateMovable :: [String] -- ^ Selected instances (if not empty)
162 -> [String] -- ^ Excluded instances
163 -> Instance.Instance -- ^ Target Instance
164 -> Instance.Instance -- ^ Target Instance with updated attribute
165 updateMovable selinsts exinsts inst =
166 if Instance.sNode inst == Node.noSecondary ||
167 Instance.name inst `elem` exinsts ||
168 not (null selinsts || Instance.name inst `elem` selinsts)
169 then Instance.setMovable inst False
172 -- | Compute the longest common suffix of a list of strings that
173 -- starts with a dot.
174 longestDomain :: [String] -> String
175 longestDomain [] = ""
176 longestDomain (x:xs) =
177 foldr (\ suffix accu -> if all (isSuffixOf suffix) xs
180 "" $ filter (isPrefixOf ".") (tails x)
182 -- | Extracts the exclusion tags from the cluster configuration.
183 extractExTags :: [String] -> [String]
185 map (drop (length exTagsPrefix)) .
186 filter (isPrefixOf exTagsPrefix)
188 -- | Extracts the common suffix from node\/instance names.
189 commonSuffix :: Node.List -> Instance.List -> String
191 let node_names = map Node.name $ Container.elems nl
192 inst_names = map Instance.name $ Container.elems il
193 in longestDomain (node_names ++ inst_names)
195 -- | Initializer function that loads the data from a node and instance
196 -- list and massages it into the correct format.
197 mergeData :: [(String, DynUtil)] -- ^ Instance utilisation data
198 -> [String] -- ^ Exclusion tags
199 -> [String] -- ^ Selected instances (if not empty)
200 -> [String] -- ^ Excluded instances
201 -> ClusterData -- ^ Data from backends
202 -> Result ClusterData -- ^ Fixed cluster data
203 mergeData um extags selinsts exinsts cdata@(ClusterData _ nl il2 tags) =
204 let il = Container.elems il2
205 il3 = foldl' (\im (name, n_util) ->
206 case Container.findByName im name of
207 Nothing -> im -- skipping unknown instance
209 let new_i = inst { Instance.util = n_util }
210 in Container.add (Instance.idx inst) new_i im
212 allextags = extags ++ extractExTags tags
213 il4 = Container.map (filterExTags allextags .
214 updateMovable selinsts exinsts) il3
215 nl2 = foldl' fixNodes nl (Container.elems il4)
216 nl3 = Container.map (flip Node.buildPeers il4) nl2
217 node_names = map Node.name (Container.elems nl)
218 inst_names = map Instance.name il
219 common_suffix = longestDomain (node_names ++ inst_names)
220 snl = Container.map (computeAlias common_suffix) nl3
221 sil = Container.map (computeAlias common_suffix) il4
222 all_inst_names = concatMap allNames $ Container.elems sil
223 in if not $ all (`elem` all_inst_names) exinsts
224 then Bad $ "Some of the excluded instances are unknown: " ++
225 show (exinsts \\ all_inst_names)
226 else if not $ all (`elem` all_inst_names) selinsts
227 then Bad $ "Some of the selected instances are unknown: " ++
228 show (selinsts \\ all_inst_names)
229 else Ok cdata { cdNodes = snl, cdInstances = sil }
231 -- | Checks the cluster data for consistency.
232 checkData :: Node.List -> Instance.List
233 -> ([String], Node.List)
237 let nname = Node.name node
238 nilst = map (`Container.find` il) (Node.pList node)
239 dilst = filter (not . Instance.running) nilst
240 adj_mem = sum . map Instance.mem $ dilst
241 delta_mem = truncate (Node.tMem node)
246 delta_dsk = truncate (Node.tDsk node)
249 newn = Node.setFmem (Node.setXmem node delta_mem)
250 (Node.fMem node - adj_mem)
251 umsg1 = [printf "node %s is missing %d MB ram \
253 nname delta_mem (delta_dsk `div` 1024) |
254 delta_mem > 512 || delta_dsk > 1024]::[String]
255 in (msgs ++ umsg1, newn)
258 -- | Compute the amount of memory used by primary instances on a node.
259 nodeImem :: Node.Node -> Instance.List -> Int
261 let rfind = flip Container.find il
262 in sum . map (Instance.mem . rfind)
265 -- | Compute the amount of disk used by instances on a node (either primary
267 nodeIdsk :: Node.Node -> Instance.List -> Int
269 let rfind = flip Container.find il
270 in sum . map (Instance.dsk . rfind)
271 $ Node.pList node ++ Node.sList node