Revision 5f6e9cb9

b/Makefile.am
583 583
# All Haskell non-test programs to be compiled but not automatically installed
584 584
HS_PROGS = $(HS_BIN_PROGS) $(HS_MYEXECLIB_PROGS)
585 585

  
586
HS_BIN_ROLES = harep hbal hscan hspace hinfo hcheck hroller
586
HS_BIN_ROLES = harep hbal hscan hspace hinfo hcheck hroller hsqueeze
587 587
HS_HTOOLS_PROGS = $(HS_BIN_ROLES) hail
588 588

  
589 589
# Haskell programs that cannot be disabled at configure (e.g., unlike
......
675 675
	src/Ganeti/HTools/Program/Hinfo.hs \
676 676
	src/Ganeti/HTools/Program/Hscan.hs \
677 677
	src/Ganeti/HTools/Program/Hspace.hs \
678
	src/Ganeti/HTools/Program/Hsqueeze.hs \
678 679
	src/Ganeti/HTools/Program/Hroller.hs \
679 680
	src/Ganeti/HTools/Program/Main.hs \
680 681
	src/Ganeti/HTools/Types.hs \
b/src/Ganeti/HTools/Program/Hsqueeze.hs
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

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

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

  
64
-- | The list of arguments supported by the program.
65
arguments :: [ArgCompletion]
66
arguments = []
67

  
68
-- | Within a cluster configuration, decide if the node hosts only
69
-- externally-mirrored instances.
70
onlyExternal ::  (Node.List, Instance.List) -> Node.Node -> Bool
71
onlyExternal (_, il) nd =
72
  not
73
  . any (Instance.usesLocalStorage . flip Container.find il)
74
  $ Node.pList nd
75

  
76
-- | Predicate whether, in a configuration, all running instances are on
77
-- online nodes.
78
allInstancesOnOnlineNodes :: (Node.List, Instance.List) -> Bool
79
allInstancesOnOnlineNodes (nl, il) =
80
 all (not . Node.offline . flip Container.find nl . Instance.pNode)
81
 . IntMap.elems
82
 $ il
83

  
84
-- | Predicate whether, in a configuration, each node has enough resources 
85
-- to additionally host the given instance.
86
allNodesCapacityFor :: Instance.Instance -> (Node.List, Instance.List) -> Bool
87
allNodesCapacityFor inst (nl, _) =
88
  all (isOk . flip Node.addPri inst) . IntMap.elems $ nl
89

  
90
-- | Balance a configuration, possible for 0 steps, till no further improvement
91
-- is possible.
92
balance :: (Node.List, Instance.List) -> (Node.List, Instance.List)
93
balance (nl, il) =
94
  let ini_cv = Cluster.compCV nl
95
      ini_tbl = Cluster.Table nl il ini_cv []
96
      balanceStep tbl = Cluster.tryBalance tbl True True False 0.0 0.0
97
      (Cluster.Table nl' il' _ _) = fromJust . last . takeWhile isJust
98
                                    $ iterate (>>= balanceStep) (Just ini_tbl)
99
  in (nl', il')
100

  
101
-- | In a configuration, mark a node as offline.
102
offlineNode :: (Node.List, Instance.List) -> Ndx -> (Node.List, Instance.List)
103
offlineNode (nl, il) ndx =
104
  let nd = Container.find ndx nl
105
      nd' = Node.setOffline nd True
106
      nl' = Container.add ndx nd' nl
107
  in (nl', il)
108

  
109
-- | Offline a list node, and return the state after a balancing attempt.
110
offlineNodes :: [Ndx] -> (Node.List, Instance.List)
111
                -> (Node.List, Instance.List)
112
offlineNodes ndxs conf =
113
  let conf' = foldl offlineNode conf ndxs
114
  in balance conf'
115

  
116
-- | Predicate on whether a list of nodes can be offlined simultaneously in a
117
-- given configuration, while still leaving enough capacity on every node for
118
-- the given instance
119
canOffline :: Instance.Instance -> [Node.Node] -> (Node.List, Instance.List)
120
              -> Bool
121
canOffline inst nds conf = 
122
  let conf' = offlineNodes (map Node.idx nds) conf
123
  in allInstancesOnOnlineNodes conf' && allNodesCapacityFor inst conf'
124

  
125
-- | Greedily offline the nodes, starting from the last element, and return
126
-- the list of nodes that could simultaneously be offlined, while keeping
127
-- the resources specified by an instance.
128
greedyOfflineNodes :: Instance.Instance -> (Node.List, Instance.List) 
129
                      -> [Node.Node] -> [Node.Node]
130
greedyOfflineNodes _ _ [] = []
131
greedyOfflineNodes inst conf (nd:nds) =
132
  let nds' = greedyOfflineNodes inst conf nds
133
  in if canOffline inst (nd:nds') conf then nd:nds' else nds'
134

  
135
-- | From a specification, name, and factor create an instance that uses that
136
-- factor times the specification, rounded down.
137
instanceFromSpecAndFactor :: String -> Double -> ISpec -> Instance.Instance
138
instanceFromSpecAndFactor name f spec =
139
  Instance.create name
140
    (floor (f * fromIntegral (iSpecMemorySize spec)))
141
    0 []
142
    (floor (f * fromIntegral (iSpecCpuCount spec)))
143
    Running [] False Node.noSecondary Node.noSecondary DTExt
144
    (floor (f * fromIntegral (iSpecSpindleUse spec)))
145
    []
146

  
147
-- | Main function.
148
main :: Options -> [String] -> IO ()
149
main opts args = do
150
  unless (null args) $ exitErr "This program doesn't take any arguments."
151

  
152
  let verbose = optVerbose opts
153
      targetf = optTargetResources opts
154

  
155
  ini_cdata@(ClusterData _ nlf ilf _ ipol) <- loadExternalData opts
156

  
157
  maybeSaveData (optSaveCluster opts) "original" "before hsqueeze run" ini_cdata
158

  
159
  let offlineCandidates = 
160
        sortBy (flip compare `on` length . Node.pList)
161
        . filter (foldl (liftA2 (&&)) (const True)
162
                  [ not . Node.offline
163
                  , not . Node.isMaster
164
                  , onlyExternal (nlf, ilf)
165
                  ])
166
        . IntMap.elems $ nlf
167
      conf = (nlf, ilf)
168
      std = iPolicyStdSpec ipol
169
      targetInstance = instanceFromSpecAndFactor "targetInstance" targetf std
170
      toOffline = greedyOfflineNodes targetInstance conf offlineCandidates
171
      (fin_nl, fin_il) = offlineNodes (map Node.idx toOffline) conf
172
      final_cdata = ini_cdata { cdNodes = fin_nl, cdInstances = fin_il }
173

  
174
  when (verbose > 1) . putStrLn 
175
    $ "Offline candidates: " ++ commaJoin (map Node.name offlineCandidates)
176

  
177
  unless (optNoHeaders opts) $
178
    putStrLn "'Nodes to offline'"
179

  
180
  mapM_ (putStrLn . Node.name) toOffline
181

  
182
  maybeSaveData (optSaveCluster opts)
183
    "squeezed" "after hsqueeze run" final_cdata
184

  
185
  
b/src/Ganeti/HTools/Program/Main.hs
43 43
import qualified Ganeti.HTools.Program.Hcheck as Hcheck
44 44
import qualified Ganeti.HTools.Program.Hscan as Hscan
45 45
import qualified Ganeti.HTools.Program.Hspace as Hspace
46
import qualified Ganeti.HTools.Program.Hsqueeze as Hsqueeze
46 47
import qualified Ganeti.HTools.Program.Hinfo as Hinfo
47 48
import qualified Ganeti.HTools.Program.Hroller as Hroller
48 49
import Ganeti.Utils
......
79 80
                "cluster rolling maintenance helper; it helps scheduling\
80 81
                \ node reboots in a manner that doesn't conflict with the\
81 82
                \ instances' topology"))
83
  , ("hsqueeze", (Hsqueeze.main, Hsqueeze.options, Hsqueeze.arguments,
84
                "cluster dynamic power management;  it powers up and down\
85
                \ nodes to keep the amount of free online resources in a\
86
                \ given range"))
82 87
  ]
83 88

  
84 89
-- | Display usage and exit.

Also available in: Unified diff