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