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 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
40 import Data.Function (on)
42 import Data.Maybe (fromJust)
43 import qualified Data.Map as M
44 import Text.Printf (printf)
46 import qualified Ganeti.HTools.Container as Container
47 import qualified Ganeti.HTools.Instance as Instance
48 import qualified Ganeti.HTools.Node as Node
50 import Ganeti.HTools.Types
54 -- | The exclusion tag prefix
55 exTagsPrefix :: String
56 exTagsPrefix = "htools:iextags:"
60 {-| The iallocator request type.
62 This type denotes what request we got from Ganeti and also holds
63 request-specific fields.
67 = Allocate Instance.Instance Int -- ^ A new instance allocation
68 | Relocate Idx Int [Ndx] -- ^ Move an instance to a new
70 | Evacuate [Ndx] -- ^ Evacuate nodes
73 -- | A complete request, as received from Ganeti.
74 data Request = Request RqType Node.List Instance.List [String]
79 -- | Lookups a node into an assoc list.
80 lookupNode :: (Monad m) => NameAssoc -> String -> String -> m Ndx
81 lookupNode ktn inst node =
82 case M.lookup node ktn of
83 Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
84 Just idx -> return idx
86 -- | Lookups an instance into an assoc list.
87 lookupInstance :: (Monad m) => NameAssoc -> String -> m Idx
88 lookupInstance kti inst =
89 case M.lookup inst kti of
90 Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
91 Just idx -> return idx
93 -- | Given a list of elements (and their names), assign indices to them.
94 assignIndices :: (Element a) =>
96 -> (NameAssoc, [(Int, a)])
99 unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
101 in (M.fromList na, idx_node)
103 -- | Assoc element comparator
104 assocEqual :: (Eq a) => (a, b) -> (a, b) -> Bool
105 assocEqual = (==) `on` fst
107 -- | For each instance, add its index to its primary and secondary nodes.
108 fixNodes :: [(Ndx, Node.Node)]
110 -> [(Ndx, Node.Node)]
113 pdx = Instance.pNode inst
114 sdx = Instance.sNode inst
115 pold = fromJust $ lookup pdx accu
116 pnew = Node.setPri pold inst
117 ac1 = deleteBy assocEqual (pdx, pold) accu
118 ac2 = (pdx, pnew):ac1
120 if sdx /= Node.noSecondary
121 then let sold = fromJust $ lookup sdx accu
122 snew = Node.setSec sold inst
123 ac3 = deleteBy assocEqual (sdx, sold) ac2
127 -- | Remove non-selected tags from the exclusion list
128 filterExTags :: [String] -> Instance.Instance -> Instance.Instance
129 filterExTags tl inst =
130 let old_tags = Instance.tags inst
131 new_tags = filter (\tag -> any (`isPrefixOf` tag) tl)
133 in inst { Instance.tags = new_tags }
135 -- | Update the movable attribute
136 updateMovable :: [String] -> Instance.Instance -> Instance.Instance
137 updateMovable exinst inst =
138 if Instance.sNode inst == Node.noSecondary ||
139 Instance.name inst `elem` exinst
140 then Instance.setMovable inst False
143 -- | Compute the longest common suffix of a list of strings that
144 -- | starts with a dot.
145 longestDomain :: [String] -> String
146 longestDomain [] = ""
147 longestDomain (x:xs) =
148 foldr (\ suffix accu -> if all (isSuffixOf suffix) xs
151 "" $ filter (isPrefixOf ".") (tails x)
153 -- | Extracts the exclusion tags from the cluster configuration
154 extractExTags :: [String] -> [String]
156 map (drop (length exTagsPrefix)) .
157 filter (isPrefixOf exTagsPrefix)
159 -- | Extracts the common suffix from node\/instance names
160 commonSuffix :: Node.List -> Instance.List -> String
162 let node_names = map Node.name $ Container.elems nl
163 inst_names = map Instance.name $ Container.elems il
164 in longestDomain (node_names ++ inst_names)
166 -- | Initializer function that loads the data from a node and instance
167 -- list and massages it into the correct format.
168 mergeData :: [(String, DynUtil)] -- ^ Instance utilisation data
169 -> [String] -- ^ Exclusion tags
170 -> [String] -- ^ Untouchable instances
171 -> (Node.AssocList, Instance.AssocList, [String])
172 -- ^ Data from backends
173 -> Result (Node.List, Instance.List, [String])
174 mergeData um extags exinsts (nl, il, tags) =
175 let il2 = Container.fromAssocList il
176 il3 = foldl' (\im (name, n_util) ->
177 case Container.findByName im name of
178 Nothing -> im -- skipping unknown instance
180 let new_i = inst { Instance.util = n_util }
181 in Container.add (Instance.idx inst) new_i im
183 allextags = extags ++ extractExTags tags
184 il4 = Container.map (filterExTags allextags .
185 updateMovable exinsts) il3
186 nl2 = foldl' fixNodes nl (Container.elems il4)
187 nl3 = Container.fromAssocList
188 (map (\ (k, v) -> (k, Node.buildPeers v il4)) nl2)
189 node_names = map (Node.name . snd) nl
190 inst_names = map (Instance.name . snd) il
191 common_suffix = longestDomain (node_names ++ inst_names)
192 snl = Container.map (computeAlias common_suffix) nl3
193 sil = Container.map (computeAlias common_suffix) il4
194 all_inst_names = concatMap allNames $ Container.elems sil
195 in if not $ all (`elem` all_inst_names) exinsts
196 then Bad $ "Some of the excluded instances are unknown: " ++
197 show (exinsts \\ all_inst_names)
198 else Ok (snl, sil, tags)
200 -- | Checks the cluster data for consistency.
201 checkData :: Node.List -> Instance.List
202 -> ([String], Node.List)
206 let nname = Node.name node
207 nilst = map (`Container.find` il) (Node.pList node)
208 dilst = filter (not . Instance.running) nilst
209 adj_mem = sum . map Instance.mem $ dilst
210 delta_mem = truncate (Node.tMem node)
215 delta_dsk = truncate (Node.tDsk node)
218 newn = Node.setFmem (Node.setXmem node delta_mem)
219 (Node.fMem node - adj_mem)
220 umsg1 = [printf "node %s is missing %d MB ram \
222 nname delta_mem (delta_dsk `div` 1024) |
223 delta_mem > 512 || delta_dsk > 1024]::[String]
224 in (msgs ++ umsg1, newn)
227 -- | Compute the amount of memory used by primary instances on a node.
228 nodeImem :: Node.Node -> Instance.List -> Int
230 let rfind = flip Container.find il
231 in sum . map (Instance.mem . rfind)
234 -- | Compute the amount of disk used by instances on a node (either primary
236 nodeIdsk :: Node.Node -> Instance.List -> Int
238 let rfind = flip Container.find il
239 in sum . map (Instance.dsk . rfind)
240 $ Node.pList node ++ Node.sList node