Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / Loader.hs @ 0f15cc76

History | View | Annotate | Download (7.3 kB)

1
{-| Generic data loader
2

    
3
This module holds the common code for parsing the input data after it
4
has been loaded from external sources.
5

    
6
-}
7

    
8
{-
9

    
10
Copyright (C) 2009 Google Inc.
11

    
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.
16

    
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.
21

    
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
25
02110-1301, USA.
26

    
27
-}
28

    
29
module Ganeti.HTools.Loader
30
    ( mergeData
31
    , checkData
32
    , assignIndices
33
    , lookupNode
34
    , lookupInstance
35
    , stripSuffix
36
    , RqType(..)
37
    , Request(..)
38
    ) where
39

    
40
import Data.Function (on)
41
import Data.List
42
import Data.Maybe (fromJust)
43
import Text.Printf (printf)
44

    
45
import qualified Ganeti.HTools.Container as Container
46
import qualified Ganeti.HTools.Instance as Instance
47
import qualified Ganeti.HTools.Node as Node
48

    
49
import Ganeti.HTools.Types
50

    
51
-- * Types
52

    
53
{-| The request type.
54

    
55
This type denotes what request we got from Ganeti and also holds
56
request-specific fields.
57

    
58
-}
59
data RqType
60
    = Allocate Instance.Instance Int -- ^ A new instance allocation
61
    | Relocate Idx Int [Ndx]         -- ^ Move an instance to a new
62
                                     -- secondary node
63
    deriving (Show)
64

    
65
-- | A complete request, as received from Ganeti.
66
data Request = Request RqType Node.List Instance.List String
67
    deriving (Show)
68

    
69
-- * Functions
70

    
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
77

    
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
84

    
85
-- | Given a list of elements (and their names), assign indices to them.
86
assignIndices :: (Element a) =>
87
                 [(String, a)]
88
              -> (NameAssoc, [(Int, a)])
89
assignIndices =
90
    unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
91
          . zip [0..]
92

    
93
-- | Assoc element comparator
94
assocEqual :: (Eq a) => (a, b) -> (a, b) -> Bool
95
assocEqual = (==) `on` fst
96

    
97
-- | For each instance, add its index to its primary and secondary nodes.
98
fixNodes :: [(Ndx, Node.Node)]
99
         -> Instance.Instance
100
         -> [(Ndx, Node.Node)]
101
fixNodes accu inst =
102
    let
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
109
    in
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
114
           in (sdx, snew):ac3
115
      else ac2
116

    
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)
122
                   old_tags
123
    in inst { Instance.tags = new_tags }
124

    
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
131
                              then suffix
132
                              else accu)
133
      "" $ filter (isPrefixOf ".") (tails x)
134

    
135
-- | Remove tail suffix from a string.
136
stripSuffix :: Int -> String -> String
137
stripSuffix sflen name = take (length name - sflen) name
138

    
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,
144
              Instance.AssocList) -- ^ Data from either Text.loadData
145
                                  -- or Rapi.loadData
146
          -> Result (Node.List, Instance.List, String)
147
mergeData um extags (nl, il) =
148
  let il2 = Container.fromAssocList il
149
      il3 = foldl' (\im (name, n_util) ->
150
                        case Container.findByName im name of
151
                          Nothing -> im -- skipping unknown instance
152
                          Just inst ->
153
                              let new_i = inst { Instance.util = n_util }
154
                              in Container.add (Instance.idx inst) new_i im
155
                   ) il2 um
156
      il4 = Container.map (filterExTags extags) il3
157
      nl2 = foldl' fixNodes nl (Container.elems il4)
158
      nl3 = Container.fromAssocList
159
            (map (\ (k, v) -> (k, Node.buildPeers v il4)) nl2)
160
      node_names = map (Node.name . snd) nl
161
      inst_names = map (Instance.name . snd) il
162
      common_suffix = longestDomain (node_names ++ inst_names)
163
      csl = length common_suffix
164
      snl = Container.map (\n -> setName n (stripSuffix csl $ nameOf n)) nl3
165
      sil = Container.map (\i -> setName i (stripSuffix csl $ nameOf i)) il4
166
  in Ok (snl, sil, common_suffix)
167

    
168
-- | Checks the cluster data for consistency.
169
checkData :: Node.List -> Instance.List
170
          -> ([String], Node.List)
171
checkData nl il =
172
    Container.mapAccum
173
        (\ msgs node ->
174
             let nname = Node.name node
175
                 nilst = map (flip Container.find il) (Node.pList node)
176
                 dilst = filter (not . Instance.running) nilst
177
                 adj_mem = sum . map Instance.mem $ dilst
178
                 delta_mem = truncate (Node.tMem node)
179
                             - Node.nMem node
180
                             - Node.fMem node
181
                             - nodeImem node il
182
                             + adj_mem
183
                 delta_dsk = truncate (Node.tDsk node)
184
                             - Node.fDsk node
185
                             - nodeIdsk node il
186
                 newn = Node.setFmem (Node.setXmem node delta_mem)
187
                        (Node.fMem node - adj_mem)
188
                 umsg1 = [printf "node %s is missing %d MB ram \
189
                                 \and %d GB disk"
190
                                 nname delta_mem (delta_dsk `div` 1024) |
191
                                 delta_mem > 512 || delta_dsk > 1024]::[String]
192
             in (msgs ++ umsg1, newn)
193
        ) [] nl
194

    
195
-- | Compute the amount of memory used by primary instances on a node.
196
nodeImem :: Node.Node -> Instance.List -> Int
197
nodeImem node il =
198
    let rfind = flip Container.find il
199
    in sum . map (Instance.mem . rfind)
200
           $ Node.pList node
201

    
202
-- | Compute the amount of disk used by instances on a node (either primary
203
-- or secondary).
204
nodeIdsk :: Node.Node -> Instance.List -> Int
205
nodeIdsk node il =
206
    let rfind = flip Container.find il
207
    in sum . map (Instance.dsk . rfind)
208
           $ Node.pList node ++ Node.sList node