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 Control.Monad (foldM)
41 import Data.Function (on)
43 import Data.Maybe (fromJust)
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
56 This type denotes what request we got from Ganeti and also holds
57 request-specific fields.
61 = Allocate Instance.Instance Int -- ^ A new instance allocation
62 | Relocate Idx Int [Ndx] -- ^ Move an instance to a new
66 -- | A complete request, as received from Ganeti.
67 data Request = Request RqType Node.List Instance.List String
72 -- | Lookups a node into an assoc list.
73 lookupNode :: (Monad m) => [(String, Ndx)] -> String -> String -> m Ndx
74 lookupNode ktn inst node =
75 case lookup node ktn of
76 Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
77 Just idx -> return idx
79 -- | Lookups an instance into an assoc list.
80 lookupInstance :: (Monad m) => [(String, Idx)] -> String -> m Idx
81 lookupInstance kti inst =
82 case lookup inst kti of
83 Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
84 Just idx -> return idx
86 -- | Given a list of elements (and their names), assign indices to them.
87 assignIndices :: (Element a) =>
89 -> (NameAssoc, [(Int, a)])
91 unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
94 -- | Assoc element comparator
95 assocEqual :: (Eq a) => (a, b) -> (a, b) -> Bool
96 assocEqual = (==) `on` fst
98 -- | For each instance, add its index to its primary and secondary nodes.
99 fixNodes :: [(Ndx, Node.Node)]
101 -> [(Ndx, Node.Node)]
104 pdx = Instance.pNode inst
105 sdx = Instance.sNode inst
106 pold = fromJust $ lookup pdx accu
107 pnew = Node.setPri pold inst
108 ac1 = deleteBy assocEqual (pdx, pold) accu
109 ac2 = (pdx, pnew):ac1
111 if sdx /= Node.noSecondary
112 then let sold = fromJust $ lookup sdx accu
113 snew = Node.setSec sold inst
114 ac3 = deleteBy assocEqual (sdx, sold) ac2
118 -- | Compute the longest common suffix of a list of strings that
119 -- | starts with a dot.
120 longestDomain :: [String] -> String
121 longestDomain [] = ""
122 longestDomain (x:xs) =
123 foldr (\ suffix accu -> if all (isSuffixOf suffix) xs
126 "" $ filter (isPrefixOf ".") (tails x)
128 -- | Remove tail suffix from a string.
129 stripSuffix :: Int -> String -> String
130 stripSuffix sflen name = take (length name - sflen) name
132 -- | Initializer function that loads the data from a node and instance
133 -- list and massages it into the correct format.
134 mergeData :: [(String, DynUtil)] -- ^ Instance utilisation data
136 Instance.AssocList) -- ^ Data from either Text.loadData
138 -> Result (Node.List, Instance.List, String)
139 mergeData um (nl, il) = do
140 let il2 = Container.fromAssocList il
141 il3 <- foldM (\im (name, n_util) -> do
142 idx <- Container.findByName im name
143 let inst = Container.find idx im
144 new_i = inst { Instance.util = n_util }
145 return $ Container.add idx new_i im
147 let nl2 = foldl' fixNodes nl (Container.elems il3)
148 let nl3 = Container.fromAssocList
149 (map (\ (k, v) -> (k, Node.buildPeers v il3)) nl2)
150 node_names = map Node.name $ Container.elems nl3
151 inst_names = map Instance.name $ Container.elems il3
152 common_suffix = longestDomain (node_names ++ inst_names)
153 csl = length common_suffix
154 snl = Container.map (\n -> setName n (stripSuffix csl $ nameOf n)) nl3
155 sil = Container.map (\i -> setName i (stripSuffix csl $ nameOf i)) il3
156 return (snl, sil, common_suffix)
158 -- | Checks the cluster data for consistency.
159 checkData :: Node.List -> Instance.List
160 -> ([String], Node.List)
164 let nname = Node.name node
165 nilst = map (flip Container.find il) (Node.pList node)
166 dilst = filter (not . Instance.running) nilst
167 adj_mem = sum . map Instance.mem $ dilst
168 delta_mem = truncate (Node.tMem node)
173 delta_dsk = truncate (Node.tDsk node)
176 newn = Node.setFmem (Node.setXmem node delta_mem)
177 (Node.fMem node - adj_mem)
178 umsg1 = [printf "node %s is missing %d MB ram \
180 nname delta_mem (delta_dsk `div` 1024) |
181 delta_mem > 512 || delta_dsk > 1024]::[String]
182 in (msgs ++ umsg1, newn)
185 -- | Compute the amount of memory used by primary instances on a node.
186 nodeImem :: Node.Node -> Instance.List -> Int
188 let rfind = flip Container.find il
189 in sum . map (Instance.mem . rfind)
192 -- | Compute the amount of disk used by instances on a node (either primary
194 nodeIdsk :: Node.Node -> Instance.List -> Int
196 let rfind = flip Container.find il
197 in sum . map (Instance.dsk . rfind)
198 $ Node.pList node ++ Node.sList node