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 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 Text.Printf (printf)
45 import qualified Ganeti.HTools.Container as Container
46 import qualified Ganeti.HTools.Instance as Instance
47 import qualified Ganeti.HTools.Node as Node
49 import Ganeti.HTools.Types
55 This type denotes what request we got from Ganeti and also holds
56 request-specific fields.
60 = Allocate Instance.Instance Int -- ^ A new instance allocation
61 | Relocate Idx Int [Ndx] -- ^ Move an instance to a new
65 -- | A complete request, as received from Ganeti.
66 data Request = Request RqType Node.List Instance.List [String] String
71 -- | Lookups a node into an assoc list.
72 lookupNode :: (Monad m) => [(String, Ndx)] -> String -> String -> m Ndx
73 lookupNode ktn inst node =
74 case lookup node ktn of
75 Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
76 Just idx -> return idx
78 -- | Lookups an instance into an assoc list.
79 lookupInstance :: (Monad m) => [(String, Idx)] -> String -> m Idx
80 lookupInstance kti inst =
81 case lookup inst kti of
82 Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
83 Just idx -> return idx
85 -- | Given a list of elements (and their names), assign indices to them.
86 assignIndices :: (Element a) =>
88 -> (NameAssoc, [(Int, a)])
90 unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
93 -- | Assoc element comparator
94 assocEqual :: (Eq a) => (a, b) -> (a, b) -> Bool
95 assocEqual = (==) `on` fst
97 -- | For each instance, add its index to its primary and secondary nodes.
98 fixNodes :: [(Ndx, Node.Node)]
100 -> [(Ndx, Node.Node)]
103 pdx = Instance.pNode inst
104 sdx = Instance.sNode inst
105 pold = fromJust $ lookup pdx accu
106 pnew = Node.setPri pold inst
107 ac1 = deleteBy assocEqual (pdx, pold) accu
108 ac2 = (pdx, pnew):ac1
110 if sdx /= Node.noSecondary
111 then let sold = fromJust $ lookup sdx accu
112 snew = Node.setSec sold inst
113 ac3 = deleteBy assocEqual (sdx, sold) ac2
117 -- | Remove non-selected tags from the exclusion list
118 filterExTags :: [String] -> Instance.Instance -> Instance.Instance
119 filterExTags tl inst =
120 let old_tags = Instance.tags inst
121 new_tags = filter (\tag -> any (\extag -> isPrefixOf extag tag) tl)
123 in inst { Instance.tags = new_tags }
125 -- | Compute the longest common suffix of a list of strings that
126 -- | starts with a dot.
127 longestDomain :: [String] -> String
128 longestDomain [] = ""
129 longestDomain (x:xs) =
130 foldr (\ suffix accu -> if all (isSuffixOf suffix) xs
133 "" $ filter (isPrefixOf ".") (tails x)
135 -- | Remove tail suffix from a string.
136 stripSuffix :: Int -> String -> String
137 stripSuffix sflen name = take (length name - sflen) name
139 -- | Initializer function that loads the data from a node and instance
140 -- list and massages it into the correct format.
141 mergeData :: [(String, DynUtil)] -- ^ Instance utilisation data
142 -> [String] -- ^ Exclusion tags
143 -> (Node.AssocList, Instance.AssocList, [String])
144 -- ^ Data from backends
145 -> Result (Node.List, Instance.List, [String], String)
146 mergeData um extags (nl, il, tags) =
147 let il2 = Container.fromAssocList il
148 il3 = foldl' (\im (name, n_util) ->
149 case Container.findByName im name of
150 Nothing -> im -- skipping unknown instance
152 let new_i = inst { Instance.util = n_util }
153 in Container.add (Instance.idx inst) new_i im
155 il4 = Container.map (filterExTags extags) il3
156 nl2 = foldl' fixNodes nl (Container.elems il4)
157 nl3 = Container.fromAssocList
158 (map (\ (k, v) -> (k, Node.buildPeers v il4)) nl2)
159 node_names = map (Node.name . snd) nl
160 inst_names = map (Instance.name . snd) il
161 common_suffix = longestDomain (node_names ++ inst_names)
162 csl = length common_suffix
163 snl = Container.map (\n -> setName n (stripSuffix csl $ nameOf n)) nl3
164 sil = Container.map (\i -> setName i (stripSuffix csl $ nameOf i)) il4
165 in Ok (snl, sil, tags, common_suffix)
167 -- | Checks the cluster data for consistency.
168 checkData :: Node.List -> Instance.List
169 -> ([String], Node.List)
173 let nname = Node.name node
174 nilst = map (flip Container.find il) (Node.pList node)
175 dilst = filter (not . Instance.running) nilst
176 adj_mem = sum . map Instance.mem $ dilst
177 delta_mem = truncate (Node.tMem node)
182 delta_dsk = truncate (Node.tDsk node)
185 newn = Node.setFmem (Node.setXmem node delta_mem)
186 (Node.fMem node - adj_mem)
187 umsg1 = [printf "node %s is missing %d MB ram \
189 nname delta_mem (delta_dsk `div` 1024) |
190 delta_mem > 512 || delta_dsk > 1024]::[String]
191 in (msgs ++ umsg1, newn)
194 -- | Compute the amount of memory used by primary instances on a node.
195 nodeImem :: Node.Node -> Instance.List -> Int
197 let rfind = flip Container.find il
198 in sum . map (Instance.mem . rfind)
201 -- | Compute the amount of disk used by instances on a node (either primary
203 nodeIdsk :: Node.Node -> Instance.List -> Int
205 let rfind = flip Container.find il
206 in sum . map (Instance.dsk . rfind)
207 $ Node.pList node ++ Node.sList node