Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Loader.hs @ c6ccc073

History | View | Annotate | Download (9.6 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, 2010, 2011 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
    , lookupGroup
36
    , commonSuffix
37
    , RelocMode(..)
38
    , RqType(..)
39
    , Request(..)
40
    , ClusterData(..)
41
    , emptyCluster
42
    ) where
43

    
44
import Data.List
45
import qualified Data.Map as M
46
import Text.Printf (printf)
47

    
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
52

    
53
import Ganeti.HTools.Types
54

    
55
-- * Constants
56

    
57
-- | The exclusion tag prefix
58
exTagsPrefix :: String
59
exTagsPrefix = "htools:iextags:"
60

    
61
-- * Types
62

    
63
-- | The iallocator multi-evac group mode type.
64
data RelocMode = KeepGroup
65
               | ChangeGroup [Gdx]
66
               | AnyGroup
67
                 deriving (Show, Read)
68

    
69
{-| The iallocator request type.
70

    
71
This type denotes what request we got from Ganeti and also holds
72
request-specific fields.
73

    
74
-}
75
data RqType
76
    = Allocate Instance.Instance Int -- ^ A new instance allocation
77
    | Relocate Idx Int [Ndx]         -- ^ Move an instance to a new
78
                                     -- secondary node
79
    | Evacuate [Ndx]                 -- ^ Evacuate nodes
80
    | MultiReloc [Idx] RelocMode     -- ^ Multi-relocate mode
81
    deriving (Show, Read)
82

    
83
-- | A complete request, as received from Ganeti.
84
data Request = Request RqType ClusterData
85
    deriving (Show, Read)
86

    
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)
94

    
95
-- | An empty cluster.
96
emptyCluster :: ClusterData
97
emptyCluster = ClusterData Container.empty Container.empty Container.empty []
98

    
99
-- * Functions
100

    
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
107

    
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
114

    
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
121

    
122
-- | Given a list of elements (and their names), assign indices to them.
123
assignIndices :: (Element a) =>
124
                 [(String, a)]
125
              -> (NameAssoc, Container.Container a)
126
assignIndices nodes =
127
  let (na, idx_node) =
128
          unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
129
          . zip [0..] $ nodes
130
  in (M.fromList na, Container.fromList idx_node)
131

    
132
-- | For each instance, add its index to its primary and secondary nodes.
133
fixNodes :: Node.List
134
         -> Instance.Instance
135
         -> Node.List
136
fixNodes accu inst =
137
    let
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
143
    in
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
148
      else ac2
149

    
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)
155
                   old_tags
156
    in inst { Instance.tags = new_tags }
157

    
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
168
    else inst
169

    
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
176
                              then suffix
177
                              else accu)
178
      "" $ filter (isPrefixOf ".") (tails x)
179

    
180
-- | Extracts the exclusion tags from the cluster configuration
181
extractExTags :: [String] -> [String]
182
extractExTags =
183
    map (drop (length exTagsPrefix)) .
184
    filter (isPrefixOf exTagsPrefix)
185

    
186
-- | Extracts the common suffix from node\/instance names
187
commonSuffix :: Node.List -> Instance.List -> String
188
commonSuffix nl il =
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)
192

    
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
206
                          Just inst ->
207
                              let new_i = inst { Instance.util = n_util }
208
                              in Container.add (Instance.idx inst) new_i im
209
                   ) il2 um
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 Ok cdata { cdNodes = snl, cdInstances = sil }
225

    
226
-- | Checks the cluster data for consistency.
227
checkData :: Node.List -> Instance.List
228
          -> ([String], Node.List)
229
checkData nl il =
230
    Container.mapAccum
231
        (\ msgs node ->
232
             let nname = Node.name node
233
                 nilst = map (`Container.find` il) (Node.pList node)
234
                 dilst = filter (not . Instance.running) nilst
235
                 adj_mem = sum . map Instance.mem $ dilst
236
                 delta_mem = truncate (Node.tMem node)
237
                             - Node.nMem node
238
                             - Node.fMem node
239
                             - nodeImem node il
240
                             + adj_mem
241
                 delta_dsk = truncate (Node.tDsk node)
242
                             - Node.fDsk node
243
                             - nodeIdsk node il
244
                 newn = Node.setFmem (Node.setXmem node delta_mem)
245
                        (Node.fMem node - adj_mem)
246
                 umsg1 = [printf "node %s is missing %d MB ram \
247
                                 \and %d GB disk"
248
                                 nname delta_mem (delta_dsk `div` 1024) |
249
                                 delta_mem > 512 || delta_dsk > 1024]::[String]
250
             in (msgs ++ umsg1, newn)
251
        ) [] nl
252

    
253
-- | Compute the amount of memory used by primary instances on a node.
254
nodeImem :: Node.Node -> Instance.List -> Int
255
nodeImem node il =
256
    let rfind = flip Container.find il
257
    in sum . map (Instance.mem . rfind)
258
           $ Node.pList node
259

    
260
-- | Compute the amount of disk used by instances on a node (either primary
261
-- or secondary).
262
nodeIdsk :: Node.Node -> Instance.List -> Int
263
nodeIdsk node il =
264
    let rfind = flip Container.find il
265
    in sum . map (Instance.dsk . rfind)
266
           $ Node.pList node ++ Node.sList node