Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / Program / Hsqueeze.hs @ 2a58a7b1

History | View | Annotate | Download (10.3 kB)

1
{-| Node freeing scheduler
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2013 Google Inc.
8

    
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
13

    
14
This program is distributed in the hope that it will be useful, but
15
WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
General Public License for more details.
18

    
19
You should have received a copy of the GNU General Public License
20
along with this program; if not, write to the Free Software
21
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22
02110-1301, USA.
23

    
24
-}
25

    
26
module Ganeti.HTools.Program.Hsqueeze
27
  (main
28
  , options
29
  , arguments
30
  ) where
31

    
32
import Control.Applicative
33
import Control.Monad
34
import Data.Function
35
import Data.List
36
import Data.Maybe
37
import qualified Data.IntMap as IntMap
38
import Text.Printf (printf)
39

    
40
import Ganeti.BasicTypes
41
import Ganeti.Common
42
import Ganeti.HTools.CLI
43
import qualified Ganeti.HTools.Container as Container
44
import qualified Ganeti.HTools.Cluster as Cluster
45
import Ganeti.HTools.ExtLoader
46
import qualified Ganeti.HTools.Instance as Instance
47
import Ganeti.HTools.Loader
48
import qualified Ganeti.HTools.Node as Node
49
import Ganeti.HTools.Types
50
import Ganeti.Utils
51

    
52
-- | Options list and functions.
53
options :: IO [OptType]
54
options = do
55
  luxi <- oLuxiSocket
56
  return
57
    [ luxi
58
    , oDataFile
59
    , oMinResources
60
    , oTargetResources
61
    , oSaveCluster
62
    , oPrintCommands
63
    , oVerbose
64
    , oNoHeaders
65
    ]
66

    
67
-- | The list of arguments supported by the program.
68
arguments :: [ArgCompletion]
69
arguments = []
70

    
71
-- | The tag-prefix indicating that hsqueeze should consider a node
72
-- as being standby.
73
standbyPrefix :: String
74
standbyPrefix = "htools:standby:"
75

    
76
-- | Predicate of having a standby tag.
77
hasStandbyTag :: Node.Node -> Bool
78
hasStandbyTag = any (standbyPrefix `isPrefixOf`) . Node.nTags
79

    
80
-- | Within a cluster configuration, decide if the node hosts only
81
-- externally-mirrored instances.
82
onlyExternal ::  (Node.List, Instance.List) -> Node.Node -> Bool
83
onlyExternal (_, il) nd =
84
  not
85
  . any (Instance.usesLocalStorage . flip Container.find il)
86
  $ Node.pList nd
87

    
88
-- | Predicate of not being secondary node for any instance
89
noSecondaries :: Node.Node -> Bool
90
noSecondaries = null . Node.sList
91

    
92
-- | Predicate whether, in a configuration, all running instances are on
93
-- online nodes.
94
allInstancesOnOnlineNodes :: (Node.List, Instance.List) -> Bool
95
allInstancesOnOnlineNodes (nl, il) =
96
 all (not . Node.offline . flip Container.find nl . Instance.pNode)
97
 . IntMap.elems
98
 $ il
99

    
100
-- | Predicate whether, in a configuration, each node has enough resources 
101
-- to additionally host the given instance.
102
allNodesCapacityFor :: Instance.Instance -> (Node.List, Instance.List) -> Bool
103
allNodesCapacityFor inst (nl, _) =
104
  all (isOk . flip Node.addPri inst) . IntMap.elems $ nl
105

    
106
-- | Balance a configuration, possible for 0 steps, till no further improvement
107
-- is possible.
108
balance :: (Node.List, Instance.List) 
109
           -> ((Node.List, Instance.List), [MoveJob])
110
balance (nl, il) =
111
  let ini_cv = Cluster.compCV nl
112
      ini_tbl = Cluster.Table nl il ini_cv []
113
      balanceStep tbl = Cluster.tryBalance tbl True True False 0.0 0.0
114
      bTables = map fromJust . takeWhile isJust
115
                  $ iterate (>>= balanceStep) (Just ini_tbl)
116
      (Cluster.Table nl' il' _ _) = last bTables
117
      moves = zip bTables (drop 1 bTables) >>= Cluster.getMoves
118
  in ((nl', il'), reverse moves)
119

    
120
-- | In a configuration, mark a node as online or offline.
121
onlineOfflineNode :: Bool -> (Node.List, Instance.List) -> Ndx ->
122
                     (Node.List, Instance.List)
123
onlineOfflineNode offline (nl, il) ndx =
124
  let nd = Container.find ndx nl
125
      nd' = Node.setOffline nd offline
126
      nl' = Container.add ndx nd' nl
127
  in (nl', il)
128

    
129
-- | Offline or online a list nodes, and return the state after a balancing
130
-- attempt together with the sequence of moves that lead there.
131
onlineOfflineNodes :: Bool -> [Ndx] -> (Node.List, Instance.List)
132
                      -> ((Node.List, Instance.List), [MoveJob])
133
onlineOfflineNodes offline ndxs conf =
134
  let conf' = foldl (onlineOfflineNode offline) conf ndxs
135
  in balance conf'
136

    
137
-- | Offline a list of nodes, and return the state after balancing with
138
-- the sequence of moves that lead there.
139
offlineNodes :: [Ndx] -> (Node.List, Instance.List)
140
                -> ((Node.List, Instance.List), [MoveJob])
141
offlineNodes = onlineOfflineNodes True
142

    
143
-- | Online a list of nodes, and return the state after balancing with
144
-- the sequence of moves that lead there.
145
onlineNodes :: [Ndx] -> (Node.List, Instance.List)
146
               -> ((Node.List, Instance.List), [MoveJob])
147
onlineNodes = onlineOfflineNodes False
148

    
149
-- | Predicate on whether a list of nodes can be offlined or onlined
150
-- simultaneously in a given configuration, while still leaving enough
151
-- capacity on every node for the given instance.
152
canOnlineOffline :: Bool -> Instance.Instance -> (Node.List, Instance.List)
153
                    -> [Node.Node] ->Bool
154
canOnlineOffline offline inst conf nds = 
155
  let conf' = fst $ onlineOfflineNodes offline (map Node.idx nds) conf
156
  in allInstancesOnOnlineNodes conf' && allNodesCapacityFor inst conf'
157

    
158
-- | Predicate on whether a list of nodes can be offlined simultaneously.
159
canOffline :: Instance.Instance -> (Node.List, Instance.List) ->
160
              [Node.Node] -> Bool
161
canOffline = canOnlineOffline True
162

    
163
-- | Predicate on whether onlining a list of nodes suffices to get enough
164
-- free resources for given instance.
165
sufficesOnline :: Instance.Instance -> (Node.List, Instance.List)
166
                  -> [Node.Node] ->  Bool
167
sufficesOnline = canOnlineOffline False
168

    
169
-- | Greedily offline the nodes, starting from the last element, and return
170
-- the list of nodes that could simultaneously be offlined, while keeping
171
-- the resources specified by an instance.
172
greedyOfflineNodes :: Instance.Instance -> (Node.List, Instance.List) 
173
                      -> [Node.Node] -> [Node.Node]
174
greedyOfflineNodes _ _ [] = []
175
greedyOfflineNodes inst conf (nd:nds) =
176
  let nds' = greedyOfflineNodes inst conf nds
177
  in if canOffline inst conf (nd:nds') then nd:nds' else nds'
178

    
179
-- | Try to provide enough resources by onlining an initial segment of
180
-- a list of nodes. Return Nothing, if even onlining all of them is not
181
-- enough.
182
tryOnline :: Instance.Instance -> (Node.List, Instance.List) -> [Node.Node]
183
             -> Maybe [Node.Node]
184
tryOnline inst conf = listToMaybe . filter (sufficesOnline inst conf) . inits
185

    
186
-- | From a specification, name, and factor create an instance that uses that
187
-- factor times the specification, rounded down.
188
instanceFromSpecAndFactor :: String -> Double -> ISpec -> Instance.Instance
189
instanceFromSpecAndFactor name f spec =
190
  Instance.create name
191
    (floor (f * fromIntegral (iSpecMemorySize spec)))
192
    0 []
193
    (floor (f * fromIntegral (iSpecCpuCount spec)))
194
    Running [] False Node.noSecondary Node.noSecondary DTExt
195
    (floor (f * fromIntegral (iSpecSpindleUse spec)))
196
    []
197

    
198
-- | Main function.
199
main :: Options -> [String] -> IO ()
200
main opts args = do
201
  unless (null args) $ exitErr "This program doesn't take any arguments."
202

    
203
  let verbose = optVerbose opts
204
      targetf = optTargetResources opts
205
      minf = optMinResources opts
206

    
207
  ini_cdata@(ClusterData _ nlf ilf _ ipol) <- loadExternalData opts
208

    
209
  maybeSaveData (optSaveCluster opts) "original" "before hsqueeze run" ini_cdata
210

    
211
  let nodelist = IntMap.elems nlf
212
      offlineCandidates = 
213
        sortBy (flip compare `on` length . Node.pList)
214
        . filter (foldl (liftA2 (&&)) (const True)
215
                  [ not . Node.offline
216
                  , not . Node.isMaster
217
                  , noSecondaries
218
                  , onlyExternal (nlf, ilf)
219
                  ])
220
        $ nodelist
221
      onlineCandidates =
222
        filter (liftA2 (&&) Node.offline hasStandbyTag) nodelist
223
      conf = (nlf, ilf)
224
      std = iPolicyStdSpec ipol
225
      targetInstance = instanceFromSpecAndFactor "targetInstance" targetf std
226
      minInstance = instanceFromSpecAndFactor "targetInstance" minf std
227
      toOffline = greedyOfflineNodes targetInstance conf offlineCandidates
228
      ((fin_off_nl, fin_off_il), off_mvs) =
229
        offlineNodes (map Node.idx toOffline) conf
230
      final_off_cdata =
231
        ini_cdata { cdNodes = fin_off_nl, cdInstances = fin_off_il }
232
      off_jobs = Cluster.splitJobs off_mvs
233
      off_cmd =
234
        Cluster.formatCmds off_jobs
235
        ++ "\necho Power Commands\n"
236
        ++ (toOffline >>= printf "  gnt-node power -f off %s\n" . Node.alias)
237
      toOnline = tryOnline minInstance conf onlineCandidates
238
      nodesToOnline = fromMaybe onlineCandidates toOnline
239
      ((fin_on_nl, fin_on_il), on_mvs) =
240
        onlineNodes (map Node.idx nodesToOnline) conf
241
      final_on_cdata =
242
        ini_cdata { cdNodes = fin_on_nl, cdInstances = fin_on_il }
243
      on_jobs = Cluster.splitJobs on_mvs
244
      on_cmd =
245
        "echo Power Commands\n"
246
        ++ (nodesToOnline >>= printf "  gnt-node power -f on %s\n" . Node.alias)
247
        ++ Cluster.formatCmds on_jobs
248

    
249
  when (verbose > 1) . putStrLn 
250
    $ "Offline candidates: " ++ commaJoin (map Node.name offlineCandidates)
251

    
252
  when (verbose > 1) . putStrLn
253
    $ "Online candidates: " ++ commaJoin (map Node.name onlineCandidates)
254

    
255
  if not (allNodesCapacityFor minInstance conf)
256
    then do
257
      unless (optNoHeaders opts) $
258
        putStrLn "'Nodes to online'"
259
      mapM_ (putStrLn . Node.name) nodesToOnline
260
      when (verbose > 1 && isNothing toOnline) . putStrLn $
261
        "Onlining all nodes will not yield enough capacity"
262
      maybeSaveCommands "Commands to run:" opts on_cmd
263
      maybeSaveData (optSaveCluster opts)
264
         "squeezed" "after hsqueeze expansion" final_on_cdata
265
    else
266
      if null toOffline
267
        then do      
268
          unless (optNoHeaders opts) $
269
            putStrLn "'No action'"
270
          maybeSaveCommands "Commands to run:" opts "echo Nothing to do"
271
          maybeSaveData (optSaveCluster opts)
272
            "squeezed" "after hsqueeze doing nothing" ini_cdata
273
        else do
274
          unless (optNoHeaders opts) $
275
            putStrLn "'Nodes to offline'"
276
          mapM_ (putStrLn . Node.name) toOffline
277
          maybeSaveCommands "Commands to run:" opts off_cmd
278
          maybeSaveData (optSaveCluster opts)
279
            "squeezed" "after hsqueeze run" final_off_cdata