Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / Cluster.hs @ 00152519

History | View | Annotate | Download (46.5 kB)

1 e4f08c46 Iustin Pop
{-| Implementation of cluster-wide logic.
2 e4f08c46 Iustin Pop
3 e4f08c46 Iustin Pop
This module holds all pure cluster-logic; I\/O related functionality
4 525bfb36 Iustin Pop
goes into the /Main/ module for the individual binaries.
5 e4f08c46 Iustin Pop
6 e4f08c46 Iustin Pop
-}
7 e4f08c46 Iustin Pop
8 e2fa2baf Iustin Pop
{-
9 e2fa2baf Iustin Pop
10 4bc33d60 Iustin Pop
Copyright (C) 2009, 2010, 2011 Google Inc.
11 e2fa2baf Iustin Pop
12 e2fa2baf Iustin Pop
This program is free software; you can redistribute it and/or modify
13 e2fa2baf Iustin Pop
it under the terms of the GNU General Public License as published by
14 e2fa2baf Iustin Pop
the Free Software Foundation; either version 2 of the License, or
15 e2fa2baf Iustin Pop
(at your option) any later version.
16 e2fa2baf Iustin Pop
17 e2fa2baf Iustin Pop
This program is distributed in the hope that it will be useful, but
18 e2fa2baf Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
19 e2fa2baf Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 e2fa2baf Iustin Pop
General Public License for more details.
21 e2fa2baf Iustin Pop
22 e2fa2baf Iustin Pop
You should have received a copy of the GNU General Public License
23 e2fa2baf Iustin Pop
along with this program; if not, write to the Free Software
24 e2fa2baf Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25 e2fa2baf Iustin Pop
02110-1301, USA.
26 e2fa2baf Iustin Pop
27 e2fa2baf Iustin Pop
-}
28 e2fa2baf Iustin Pop
29 669d7e3d Iustin Pop
module Ganeti.HTools.Cluster
30 e4f08c46 Iustin Pop
    (
31 e4f08c46 Iustin Pop
     -- * Types
32 85d0ddc3 Iustin Pop
      AllocSolution(..)
33 e4f08c46 Iustin Pop
    , Table(..)
34 1a7eff0e Iustin Pop
    , CStats(..)
35 9b8fac3d Iustin Pop
    , AllocStats
36 e4f08c46 Iustin Pop
    -- * Generic functions
37 e4f08c46 Iustin Pop
    , totalResources
38 9b8fac3d Iustin Pop
    , computeAllocationDelta
39 e4f08c46 Iustin Pop
    -- * First phase functions
40 e4f08c46 Iustin Pop
    , computeBadItems
41 e4f08c46 Iustin Pop
    -- * Second phase functions
42 7dfaafb1 Iustin Pop
    , printSolutionLine
43 142538ff Iustin Pop
    , formatCmds
44 0e8ae201 Iustin Pop
    , involvedNodes
45 0e8ae201 Iustin Pop
    , splitJobs
46 507fda3f Iustin Pop
    -- * Display functions
47 507fda3f Iustin Pop
    , printNodes
48 507fda3f Iustin Pop
    , printInsts
49 e4f08c46 Iustin Pop
    -- * Balacing functions
50 e4f08c46 Iustin Pop
    , checkMove
51 5ad86777 Iustin Pop
    , doNextBalance
52 f25e5aac Iustin Pop
    , tryBalance
53 e4f08c46 Iustin Pop
    , compCV
54 6bc39970 Iustin Pop
    , compDetailedCV
55 e4f08c46 Iustin Pop
    , printStats
56 6b20875c Iustin Pop
    , iMoveToJob
57 4a340313 Iustin Pop
    -- * IAllocator functions
58 6cb1649f Iustin Pop
    , genAllocNodes
59 dbba5246 Iustin Pop
    , tryAlloc
60 9b1584fc Iustin Pop
    , tryMGAlloc
61 dbba5246 Iustin Pop
    , tryReloc
62 4bc33d60 Iustin Pop
    , tryMGReloc
63 12b0511d Iustin Pop
    , tryEvac
64 1bc47d38 Iustin Pop
    , tryMGEvac
65 478df686 Iustin Pop
    , collapseFailures
66 3ce8009a Iustin Pop
    -- * Allocation functions
67 3ce8009a Iustin Pop
    , iterateAlloc
68 3ce8009a Iustin Pop
    , tieredAlloc
69 949397c8 Iustin Pop
    , tieredSpecMap
70 949397c8 Iustin Pop
     -- * Node group functions
71 32b8d9c0 Iustin Pop
    , instanceGroup
72 32b8d9c0 Iustin Pop
    , findSplitInstances
73 f4161783 Iustin Pop
    , splitCluster
74 e4f08c46 Iustin Pop
    ) where
75 e4f08c46 Iustin Pop
76 949397c8 Iustin Pop
import Data.Function (on)
77 e4f08c46 Iustin Pop
import Data.List
78 5182e970 Iustin Pop
import Data.Ord (comparing)
79 e4f08c46 Iustin Pop
import Text.Printf (printf)
80 9d3fada5 Iustin Pop
import Control.Monad
81 a3eee4ad Iustin Pop
import Control.Parallel.Strategies
82 e4f08c46 Iustin Pop
83 669d7e3d Iustin Pop
import qualified Ganeti.HTools.Container as Container
84 669d7e3d Iustin Pop
import qualified Ganeti.HTools.Instance as Instance
85 669d7e3d Iustin Pop
import qualified Ganeti.HTools.Node as Node
86 aec636b9 Iustin Pop
import qualified Ganeti.HTools.Group as Group
87 e4c5beaf Iustin Pop
import Ganeti.HTools.Types
88 669d7e3d Iustin Pop
import Ganeti.HTools.Utils
89 6b20875c Iustin Pop
import qualified Ganeti.OpCodes as OpCodes
90 e4f08c46 Iustin Pop
91 9188aeef Iustin Pop
-- * Types
92 9188aeef Iustin Pop
93 0c936d24 Iustin Pop
-- | Allocation\/relocation solution.
94 85d0ddc3 Iustin Pop
data AllocSolution = AllocSolution
95 85d0ddc3 Iustin Pop
  { asFailures  :: [FailMode]          -- ^ Failure counts
96 85d0ddc3 Iustin Pop
  , asAllocs    :: Int                 -- ^ Good allocation count
97 85d0ddc3 Iustin Pop
  , asSolutions :: [Node.AllocElement] -- ^ The actual result, length
98 85d0ddc3 Iustin Pop
                                       -- of the list depends on the
99 85d0ddc3 Iustin Pop
                                       -- allocation/relocation mode
100 859fc11d Iustin Pop
  , asLog       :: [String]            -- ^ A list of informational messages
101 85d0ddc3 Iustin Pop
  }
102 85d0ddc3 Iustin Pop
103 40ee14bc Iustin Pop
-- | Allocation results, as used in 'iterateAlloc' and 'tieredAlloc'.
104 40ee14bc Iustin Pop
type AllocResult = (FailStats, Node.List, Instance.List,
105 40ee14bc Iustin Pop
                    [Instance.Instance], [CStats])
106 40ee14bc Iustin Pop
107 6cb1649f Iustin Pop
108 6cb1649f Iustin Pop
-- | A type denoting the valid allocation mode/pairs.
109 525bfb36 Iustin Pop
--
110 6cb1649f Iustin Pop
-- For a one-node allocation, this will be a @Left ['Node.Node']@,
111 6cb1649f Iustin Pop
-- whereas for a two-node allocation, this will be a @Right
112 6cb1649f Iustin Pop
-- [('Node.Node', 'Node.Node')]@.
113 0d66ea67 Iustin Pop
type AllocNodes = Either [Ndx] [(Ndx, Ndx)]
114 6cb1649f Iustin Pop
115 525bfb36 Iustin Pop
-- | The empty solution we start with when computing allocations.
116 85d0ddc3 Iustin Pop
emptySolution :: AllocSolution
117 85d0ddc3 Iustin Pop
emptySolution = AllocSolution { asFailures = [], asAllocs = 0
118 859fc11d Iustin Pop
                              , asSolutions = [], asLog = [] }
119 78694255 Iustin Pop
120 525bfb36 Iustin Pop
-- | The complete state for the balancing solution.
121 262a08a2 Iustin Pop
data Table = Table Node.List Instance.List Score [Placement]
122 6bc39970 Iustin Pop
             deriving (Show, Read)
123 e4f08c46 Iustin Pop
124 7034694d Iustin Pop
data CStats = CStats { csFmem :: Integer -- ^ Cluster free mem
125 7034694d Iustin Pop
                     , csFdsk :: Integer -- ^ Cluster free disk
126 7034694d Iustin Pop
                     , csAmem :: Integer -- ^ Cluster allocatable mem
127 7034694d Iustin Pop
                     , csAdsk :: Integer -- ^ Cluster allocatable disk
128 7034694d Iustin Pop
                     , csAcpu :: Integer -- ^ Cluster allocatable cpus
129 7034694d Iustin Pop
                     , csMmem :: Integer -- ^ Max node allocatable mem
130 7034694d Iustin Pop
                     , csMdsk :: Integer -- ^ Max node allocatable disk
131 7034694d Iustin Pop
                     , csMcpu :: Integer -- ^ Max node allocatable cpu
132 7034694d Iustin Pop
                     , csImem :: Integer -- ^ Instance used mem
133 7034694d Iustin Pop
                     , csIdsk :: Integer -- ^ Instance used disk
134 7034694d Iustin Pop
                     , csIcpu :: Integer -- ^ Instance used cpu
135 7034694d Iustin Pop
                     , csTmem :: Double  -- ^ Cluster total mem
136 7034694d Iustin Pop
                     , csTdsk :: Double  -- ^ Cluster total disk
137 7034694d Iustin Pop
                     , csTcpu :: Double  -- ^ Cluster total cpus
138 7034694d Iustin Pop
                     , csVcpu :: Integer -- ^ Cluster virtual cpus (if
139 7034694d Iustin Pop
                                         -- node pCpu has been set,
140 7034694d Iustin Pop
                                         -- otherwise -1)
141 7034694d Iustin Pop
                     , csXmem :: Integer -- ^ Unnacounted for mem
142 7034694d Iustin Pop
                     , csNmem :: Integer -- ^ Node own memory
143 7034694d Iustin Pop
                     , csScore :: Score  -- ^ The cluster score
144 7034694d Iustin Pop
                     , csNinst :: Int    -- ^ The total number of instances
145 1a7eff0e Iustin Pop
                     }
146 6bc39970 Iustin Pop
            deriving (Show, Read)
147 1a7eff0e Iustin Pop
148 525bfb36 Iustin Pop
-- | Currently used, possibly to allocate, unallocable.
149 9b8fac3d Iustin Pop
type AllocStats = (RSpec, RSpec, RSpec)
150 9b8fac3d Iustin Pop
151 9188aeef Iustin Pop
-- * Utility functions
152 9188aeef Iustin Pop
153 e4f08c46 Iustin Pop
-- | Verifies the N+1 status and return the affected nodes.
154 e4f08c46 Iustin Pop
verifyN1 :: [Node.Node] -> [Node.Node]
155 9f6dcdea Iustin Pop
verifyN1 = filter Node.failN1
156 e4f08c46 Iustin Pop
157 9188aeef Iustin Pop
{-| Computes the pair of bad nodes and instances.
158 9188aeef Iustin Pop
159 9188aeef Iustin Pop
The bad node list is computed via a simple 'verifyN1' check, and the
160 9188aeef Iustin Pop
bad instance list is the list of primary and secondary instances of
161 9188aeef Iustin Pop
those nodes.
162 9188aeef Iustin Pop
163 9188aeef Iustin Pop
-}
164 9188aeef Iustin Pop
computeBadItems :: Node.List -> Instance.List ->
165 9188aeef Iustin Pop
                   ([Node.Node], [Instance.Instance])
166 9188aeef Iustin Pop
computeBadItems nl il =
167 dbba5246 Iustin Pop
  let bad_nodes = verifyN1 $ getOnline nl
168 5182e970 Iustin Pop
      bad_instances = map (`Container.find` il) .
169 9f6dcdea Iustin Pop
                      sort . nub $
170 2060348b Iustin Pop
                      concatMap (\ n -> Node.sList n ++ Node.pList n) bad_nodes
171 9188aeef Iustin Pop
  in
172 9188aeef Iustin Pop
    (bad_nodes, bad_instances)
173 9188aeef Iustin Pop
174 525bfb36 Iustin Pop
-- | Zero-initializer for the CStats type.
175 1a7eff0e Iustin Pop
emptyCStats :: CStats
176 86ecce4a Iustin Pop
emptyCStats = CStats 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
177 1a7eff0e Iustin Pop
178 525bfb36 Iustin Pop
-- | Update stats with data from a new node.
179 1a7eff0e Iustin Pop
updateCStats :: CStats -> Node.Node -> CStats
180 1a7eff0e Iustin Pop
updateCStats cs node =
181 f5b553da Iustin Pop
    let CStats { csFmem = x_fmem, csFdsk = x_fdsk,
182 f5b553da Iustin Pop
                 csAmem = x_amem, csAcpu = x_acpu, csAdsk = x_adsk,
183 f5b553da Iustin Pop
                 csMmem = x_mmem, csMdsk = x_mdsk, csMcpu = x_mcpu,
184 f5b553da Iustin Pop
                 csImem = x_imem, csIdsk = x_idsk, csIcpu = x_icpu,
185 f5b553da Iustin Pop
                 csTmem = x_tmem, csTdsk = x_tdsk, csTcpu = x_tcpu,
186 86ecce4a Iustin Pop
                 csVcpu = x_vcpu,
187 f5b553da Iustin Pop
                 csXmem = x_xmem, csNmem = x_nmem, csNinst = x_ninst
188 8c4c6a8a Iustin Pop
               }
189 1a7eff0e Iustin Pop
            = cs
190 2060348b Iustin Pop
        inc_amem = Node.fMem node - Node.rMem node
191 1a7eff0e Iustin Pop
        inc_amem' = if inc_amem > 0 then inc_amem else 0
192 301789f4 Iustin Pop
        inc_adsk = Node.availDisk node
193 2060348b Iustin Pop
        inc_imem = truncate (Node.tMem node) - Node.nMem node
194 2060348b Iustin Pop
                   - Node.xMem node - Node.fMem node
195 2060348b Iustin Pop
        inc_icpu = Node.uCpu node
196 2060348b Iustin Pop
        inc_idsk = truncate (Node.tDsk node) - Node.fDsk node
197 86ecce4a Iustin Pop
        inc_vcpu = Node.hiCpu node
198 f52dadb2 Iustin Pop
        inc_acpu = Node.availCpu node
199 8c4c6a8a Iustin Pop
200 7034694d Iustin Pop
    in cs { csFmem = x_fmem + fromIntegral (Node.fMem node)
201 7034694d Iustin Pop
          , csFdsk = x_fdsk + fromIntegral (Node.fDsk node)
202 7034694d Iustin Pop
          , csAmem = x_amem + fromIntegral inc_amem'
203 7034694d Iustin Pop
          , csAdsk = x_adsk + fromIntegral inc_adsk
204 7034694d Iustin Pop
          , csAcpu = x_acpu + fromIntegral inc_acpu
205 7034694d Iustin Pop
          , csMmem = max x_mmem (fromIntegral inc_amem')
206 7034694d Iustin Pop
          , csMdsk = max x_mdsk (fromIntegral inc_adsk)
207 7034694d Iustin Pop
          , csMcpu = max x_mcpu (fromIntegral inc_acpu)
208 7034694d Iustin Pop
          , csImem = x_imem + fromIntegral inc_imem
209 7034694d Iustin Pop
          , csIdsk = x_idsk + fromIntegral inc_idsk
210 7034694d Iustin Pop
          , csIcpu = x_icpu + fromIntegral inc_icpu
211 f5b553da Iustin Pop
          , csTmem = x_tmem + Node.tMem node
212 f5b553da Iustin Pop
          , csTdsk = x_tdsk + Node.tDsk node
213 f5b553da Iustin Pop
          , csTcpu = x_tcpu + Node.tCpu node
214 7034694d Iustin Pop
          , csVcpu = x_vcpu + fromIntegral inc_vcpu
215 7034694d Iustin Pop
          , csXmem = x_xmem + fromIntegral (Node.xMem node)
216 7034694d Iustin Pop
          , csNmem = x_nmem + fromIntegral (Node.nMem node)
217 f5b553da Iustin Pop
          , csNinst = x_ninst + length (Node.pList node)
218 de4ac2c2 Iustin Pop
          }
219 1a7eff0e Iustin Pop
220 9188aeef Iustin Pop
-- | Compute the total free disk and memory in the cluster.
221 1a7eff0e Iustin Pop
totalResources :: Node.List -> CStats
222 de4ac2c2 Iustin Pop
totalResources nl =
223 de4ac2c2 Iustin Pop
    let cs = foldl' updateCStats emptyCStats . Container.elems $ nl
224 f5b553da Iustin Pop
    in cs { csScore = compCV nl }
225 9188aeef Iustin Pop
226 9b8fac3d Iustin Pop
-- | Compute the delta between two cluster state.
227 9b8fac3d Iustin Pop
--
228 9b8fac3d Iustin Pop
-- This is used when doing allocations, to understand better the
229 e2436511 Iustin Pop
-- available cluster resources. The return value is a triple of the
230 e2436511 Iustin Pop
-- current used values, the delta that was still allocated, and what
231 e2436511 Iustin Pop
-- was left unallocated.
232 9b8fac3d Iustin Pop
computeAllocationDelta :: CStats -> CStats -> AllocStats
233 9b8fac3d Iustin Pop
computeAllocationDelta cini cfin =
234 9b8fac3d Iustin Pop
    let CStats {csImem = i_imem, csIdsk = i_idsk, csIcpu = i_icpu} = cini
235 9b8fac3d Iustin Pop
        CStats {csImem = f_imem, csIdsk = f_idsk, csIcpu = f_icpu,
236 9b8fac3d Iustin Pop
                csTmem = t_mem, csTdsk = t_dsk, csVcpu = v_cpu } = cfin
237 7034694d Iustin Pop
        rini = RSpec (fromIntegral i_icpu) (fromIntegral i_imem)
238 7034694d Iustin Pop
               (fromIntegral i_idsk)
239 7034694d Iustin Pop
        rfin = RSpec (fromIntegral (f_icpu - i_icpu))
240 7034694d Iustin Pop
               (fromIntegral (f_imem - i_imem))
241 7034694d Iustin Pop
               (fromIntegral (f_idsk - i_idsk))
242 7034694d Iustin Pop
        un_cpu = fromIntegral (v_cpu - f_icpu)::Int
243 7034694d Iustin Pop
        runa = RSpec un_cpu (truncate t_mem - fromIntegral f_imem)
244 7034694d Iustin Pop
               (truncate t_dsk - fromIntegral f_idsk)
245 9b8fac3d Iustin Pop
    in (rini, rfin, runa)
246 9b8fac3d Iustin Pop
247 525bfb36 Iustin Pop
-- | The names and weights of the individual elements in the CV list.
248 8a3b30ca Iustin Pop
detailedCVInfo :: [(Double, String)]
249 8a3b30ca Iustin Pop
detailedCVInfo = [ (1,  "free_mem_cv")
250 8a3b30ca Iustin Pop
                 , (1,  "free_disk_cv")
251 8a3b30ca Iustin Pop
                 , (1,  "n1_cnt")
252 8a3b30ca Iustin Pop
                 , (1,  "reserved_mem_cv")
253 8a3b30ca Iustin Pop
                 , (4,  "offline_all_cnt")
254 8a3b30ca Iustin Pop
                 , (16, "offline_pri_cnt")
255 8a3b30ca Iustin Pop
                 , (1,  "vcpu_ratio_cv")
256 8a3b30ca Iustin Pop
                 , (1,  "cpu_load_cv")
257 8a3b30ca Iustin Pop
                 , (1,  "mem_load_cv")
258 8a3b30ca Iustin Pop
                 , (1,  "disk_load_cv")
259 8a3b30ca Iustin Pop
                 , (1,  "net_load_cv")
260 306cccd5 Iustin Pop
                 , (2,  "pri_tags_score")
261 8a3b30ca Iustin Pop
                 ]
262 8a3b30ca Iustin Pop
263 8a3b30ca Iustin Pop
detailedCVWeights :: [Double]
264 8a3b30ca Iustin Pop
detailedCVWeights = map fst detailedCVInfo
265 fca250e9 Iustin Pop
266 9188aeef Iustin Pop
-- | Compute the mem and disk covariance.
267 fca250e9 Iustin Pop
compDetailedCV :: Node.List -> [Double]
268 9188aeef Iustin Pop
compDetailedCV nl =
269 9188aeef Iustin Pop
    let
270 9188aeef Iustin Pop
        all_nodes = Container.elems nl
271 9188aeef Iustin Pop
        (offline, nodes) = partition Node.offline all_nodes
272 2060348b Iustin Pop
        mem_l = map Node.pMem nodes
273 2060348b Iustin Pop
        dsk_l = map Node.pDsk nodes
274 daee4bed Iustin Pop
        -- metric: memory covariance
275 4715711d Iustin Pop
        mem_cv = stdDev mem_l
276 daee4bed Iustin Pop
        -- metric: disk covariance
277 4715711d Iustin Pop
        dsk_cv = stdDev dsk_l
278 c3c7a0c1 Iustin Pop
        -- metric: count of instances living on N1 failing nodes
279 c3c7a0c1 Iustin Pop
        n1_score = fromIntegral . sum . map (\n -> length (Node.sList n) +
280 c3c7a0c1 Iustin Pop
                                                   length (Node.pList n)) .
281 c3c7a0c1 Iustin Pop
                   filter Node.failN1 $ nodes :: Double
282 2060348b Iustin Pop
        res_l = map Node.pRem nodes
283 daee4bed Iustin Pop
        -- metric: reserved memory covariance
284 4715711d Iustin Pop
        res_cv = stdDev res_l
285 e4d31268 Iustin Pop
        -- offline instances metrics
286 e4d31268 Iustin Pop
        offline_ipri = sum . map (length . Node.pList) $ offline
287 e4d31268 Iustin Pop
        offline_isec = sum . map (length . Node.sList) $ offline
288 e4d31268 Iustin Pop
        -- metric: count of instances on offline nodes
289 e4d31268 Iustin Pop
        off_score = fromIntegral (offline_ipri + offline_isec)::Double
290 673f0f00 Iustin Pop
        -- metric: count of primary instances on offline nodes (this
291 673f0f00 Iustin Pop
        -- helps with evacuation/failover of primary instances on
292 673f0f00 Iustin Pop
        -- 2-node clusters with one node offline)
293 673f0f00 Iustin Pop
        off_pri_score = fromIntegral offline_ipri::Double
294 2060348b Iustin Pop
        cpu_l = map Node.pCpu nodes
295 daee4bed Iustin Pop
        -- metric: covariance of vcpu/pcpu ratio
296 4715711d Iustin Pop
        cpu_cv = stdDev cpu_l
297 daee4bed Iustin Pop
        -- metrics: covariance of cpu, memory, disk and network load
298 ee9724b9 Iustin Pop
        (c_load, m_load, d_load, n_load) = unzip4 $
299 ee9724b9 Iustin Pop
            map (\n ->
300 ee9724b9 Iustin Pop
                     let DynUtil c1 m1 d1 n1 = Node.utilLoad n
301 ee9724b9 Iustin Pop
                         DynUtil c2 m2 d2 n2 = Node.utilPool n
302 ee9724b9 Iustin Pop
                     in (c1/c2, m1/m2, d1/d2, n1/n2)
303 ee9724b9 Iustin Pop
                ) nodes
304 d844fe88 Iustin Pop
        -- metric: conflicting instance count
305 d844fe88 Iustin Pop
        pri_tags_inst = sum $ map Node.conflictingPrimaries nodes
306 d844fe88 Iustin Pop
        pri_tags_score = fromIntegral pri_tags_inst::Double
307 673f0f00 Iustin Pop
    in [ mem_cv, dsk_cv, n1_score, res_cv, off_score, off_pri_score, cpu_cv
308 4715711d Iustin Pop
       , stdDev c_load, stdDev m_load , stdDev d_load, stdDev n_load
309 d844fe88 Iustin Pop
       , pri_tags_score ]
310 9188aeef Iustin Pop
311 9188aeef Iustin Pop
-- | Compute the /total/ variance.
312 9188aeef Iustin Pop
compCV :: Node.List -> Double
313 8a3b30ca Iustin Pop
compCV = sum . zipWith (*) detailedCVWeights . compDetailedCV
314 9188aeef Iustin Pop
315 525bfb36 Iustin Pop
-- | Compute online nodes from a 'Node.List'.
316 dbba5246 Iustin Pop
getOnline :: Node.List -> [Node.Node]
317 dbba5246 Iustin Pop
getOnline = filter (not . Node.offline) . Container.elems
318 dbba5246 Iustin Pop
319 525bfb36 Iustin Pop
-- * Balancing functions
320 9188aeef Iustin Pop
321 9188aeef Iustin Pop
-- | Compute best table. Note that the ordering of the arguments is important.
322 9188aeef Iustin Pop
compareTables :: Table -> Table -> Table
323 9188aeef Iustin Pop
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
324 9188aeef Iustin Pop
    if a_cv > b_cv then b else a
325 9188aeef Iustin Pop
326 9188aeef Iustin Pop
-- | Applies an instance move to a given node list and instance.
327 262a08a2 Iustin Pop
applyMove :: Node.List -> Instance.Instance
328 8880d889 Iustin Pop
          -> IMove -> OpResult (Node.List, Instance.Instance, Ndx, Ndx)
329 00b51a14 Iustin Pop
-- Failover (f)
330 e4f08c46 Iustin Pop
applyMove nl inst Failover =
331 2060348b Iustin Pop
    let old_pdx = Instance.pNode inst
332 2060348b Iustin Pop
        old_sdx = Instance.sNode inst
333 e4f08c46 Iustin Pop
        old_p = Container.find old_pdx nl
334 e4f08c46 Iustin Pop
        old_s = Container.find old_sdx nl
335 e4f08c46 Iustin Pop
        int_p = Node.removePri old_p inst
336 e4f08c46 Iustin Pop
        int_s = Node.removeSec old_s inst
337 2cae47e9 Iustin Pop
        force_p = Node.offline old_p
338 b161386d Iustin Pop
        new_nl = do -- Maybe monad
339 2cae47e9 Iustin Pop
          new_p <- Node.addPriEx force_p int_s inst
340 b161386d Iustin Pop
          new_s <- Node.addSec int_p inst old_sdx
341 8880d889 Iustin Pop
          let new_inst = Instance.setBoth inst old_sdx old_pdx
342 8880d889 Iustin Pop
          return (Container.addTwo old_pdx new_s old_sdx new_p nl,
343 8880d889 Iustin Pop
                  new_inst, old_sdx, old_pdx)
344 8880d889 Iustin Pop
    in new_nl
345 e4f08c46 Iustin Pop
346 00b51a14 Iustin Pop
-- Replace the primary (f:, r:np, f)
347 e4f08c46 Iustin Pop
applyMove nl inst (ReplacePrimary new_pdx) =
348 2060348b Iustin Pop
    let old_pdx = Instance.pNode inst
349 2060348b Iustin Pop
        old_sdx = Instance.sNode inst
350 e4f08c46 Iustin Pop
        old_p = Container.find old_pdx nl
351 e4f08c46 Iustin Pop
        old_s = Container.find old_sdx nl
352 e4f08c46 Iustin Pop
        tgt_n = Container.find new_pdx nl
353 e4f08c46 Iustin Pop
        int_p = Node.removePri old_p inst
354 e4f08c46 Iustin Pop
        int_s = Node.removeSec old_s inst
355 2cae47e9 Iustin Pop
        force_p = Node.offline old_p
356 b161386d Iustin Pop
        new_nl = do -- Maybe monad
357 70db354e Iustin Pop
          -- check that the current secondary can host the instance
358 70db354e Iustin Pop
          -- during the migration
359 2cae47e9 Iustin Pop
          tmp_s <- Node.addPriEx force_p int_s inst
360 70db354e Iustin Pop
          let tmp_s' = Node.removePri tmp_s inst
361 2cae47e9 Iustin Pop
          new_p <- Node.addPriEx force_p tgt_n inst
362 2cae47e9 Iustin Pop
          new_s <- Node.addSecEx force_p tmp_s' inst new_pdx
363 8880d889 Iustin Pop
          let new_inst = Instance.setPri inst new_pdx
364 8880d889 Iustin Pop
          return (Container.add new_pdx new_p $
365 8880d889 Iustin Pop
                  Container.addTwo old_pdx int_p old_sdx new_s nl,
366 8880d889 Iustin Pop
                  new_inst, new_pdx, old_sdx)
367 8880d889 Iustin Pop
    in new_nl
368 e4f08c46 Iustin Pop
369 00b51a14 Iustin Pop
-- Replace the secondary (r:ns)
370 e4f08c46 Iustin Pop
applyMove nl inst (ReplaceSecondary new_sdx) =
371 2060348b Iustin Pop
    let old_pdx = Instance.pNode inst
372 2060348b Iustin Pop
        old_sdx = Instance.sNode inst
373 e4f08c46 Iustin Pop
        old_s = Container.find old_sdx nl
374 e4f08c46 Iustin Pop
        tgt_n = Container.find new_sdx nl
375 e4f08c46 Iustin Pop
        int_s = Node.removeSec old_s inst
376 2cae47e9 Iustin Pop
        force_s = Node.offline old_s
377 8880d889 Iustin Pop
        new_inst = Instance.setSec inst new_sdx
378 2cae47e9 Iustin Pop
        new_nl = Node.addSecEx force_s tgt_n inst old_pdx >>=
379 8880d889 Iustin Pop
                 \new_s -> return (Container.addTwo new_sdx
380 8880d889 Iustin Pop
                                   new_s old_sdx int_s nl,
381 8880d889 Iustin Pop
                                   new_inst, old_pdx, new_sdx)
382 8880d889 Iustin Pop
    in new_nl
383 e4f08c46 Iustin Pop
384 00b51a14 Iustin Pop
-- Replace the secondary and failover (r:np, f)
385 79ac6b6f Iustin Pop
applyMove nl inst (ReplaceAndFailover new_pdx) =
386 2060348b Iustin Pop
    let old_pdx = Instance.pNode inst
387 2060348b Iustin Pop
        old_sdx = Instance.sNode inst
388 79ac6b6f Iustin Pop
        old_p = Container.find old_pdx nl
389 79ac6b6f Iustin Pop
        old_s = Container.find old_sdx nl
390 79ac6b6f Iustin Pop
        tgt_n = Container.find new_pdx nl
391 79ac6b6f Iustin Pop
        int_p = Node.removePri old_p inst
392 79ac6b6f Iustin Pop
        int_s = Node.removeSec old_s inst
393 2cae47e9 Iustin Pop
        force_s = Node.offline old_s
394 b161386d Iustin Pop
        new_nl = do -- Maybe monad
395 b161386d Iustin Pop
          new_p <- Node.addPri tgt_n inst
396 2cae47e9 Iustin Pop
          new_s <- Node.addSecEx force_s int_p inst new_pdx
397 8880d889 Iustin Pop
          let new_inst = Instance.setBoth inst new_pdx old_pdx
398 8880d889 Iustin Pop
          return (Container.add new_pdx new_p $
399 8880d889 Iustin Pop
                  Container.addTwo old_pdx new_s old_sdx int_s nl,
400 8880d889 Iustin Pop
                  new_inst, new_pdx, old_pdx)
401 8880d889 Iustin Pop
    in new_nl
402 79ac6b6f Iustin Pop
403 19493d33 Iustin Pop
-- Failver and replace the secondary (f, r:ns)
404 19493d33 Iustin Pop
applyMove nl inst (FailoverAndReplace new_sdx) =
405 2060348b Iustin Pop
    let old_pdx = Instance.pNode inst
406 2060348b Iustin Pop
        old_sdx = Instance.sNode inst
407 19493d33 Iustin Pop
        old_p = Container.find old_pdx nl
408 19493d33 Iustin Pop
        old_s = Container.find old_sdx nl
409 19493d33 Iustin Pop
        tgt_n = Container.find new_sdx nl
410 19493d33 Iustin Pop
        int_p = Node.removePri old_p inst
411 19493d33 Iustin Pop
        int_s = Node.removeSec old_s inst
412 2cae47e9 Iustin Pop
        force_p = Node.offline old_p
413 b161386d Iustin Pop
        new_nl = do -- Maybe monad
414 2cae47e9 Iustin Pop
          new_p <- Node.addPriEx force_p int_s inst
415 2cae47e9 Iustin Pop
          new_s <- Node.addSecEx force_p tgt_n inst old_sdx
416 8880d889 Iustin Pop
          let new_inst = Instance.setBoth inst old_sdx new_sdx
417 8880d889 Iustin Pop
          return (Container.add new_sdx new_s $
418 8880d889 Iustin Pop
                  Container.addTwo old_sdx new_p old_pdx int_p nl,
419 8880d889 Iustin Pop
                  new_inst, old_sdx, new_sdx)
420 8880d889 Iustin Pop
    in new_nl
421 19493d33 Iustin Pop
422 9188aeef Iustin Pop
-- | Tries to allocate an instance on one given node.
423 0d66ea67 Iustin Pop
allocateOnSingle :: Node.List -> Instance.Instance -> Ndx
424 1fe81531 Iustin Pop
                 -> OpResult Node.AllocElement
425 0d66ea67 Iustin Pop
allocateOnSingle nl inst new_pdx =
426 0d66ea67 Iustin Pop
    let p = Container.find new_pdx nl
427 8880d889 Iustin Pop
        new_inst = Instance.setBoth inst new_pdx Node.noSecondary
428 7d3f4253 Iustin Pop
    in  Node.addPri p inst >>= \new_p -> do
429 7d3f4253 Iustin Pop
      let new_nl = Container.add new_pdx new_p nl
430 7d3f4253 Iustin Pop
          new_score = compCV nl
431 7d3f4253 Iustin Pop
      return (new_nl, new_inst, [new_p], new_score)
432 5e15f460 Iustin Pop
433 9188aeef Iustin Pop
-- | Tries to allocate an instance on a given pair of nodes.
434 0d66ea67 Iustin Pop
allocateOnPair :: Node.List -> Instance.Instance -> Ndx -> Ndx
435 1fe81531 Iustin Pop
               -> OpResult Node.AllocElement
436 0d66ea67 Iustin Pop
allocateOnPair nl inst new_pdx new_sdx =
437 0d66ea67 Iustin Pop
    let tgt_p = Container.find new_pdx nl
438 0d66ea67 Iustin Pop
        tgt_s = Container.find new_sdx nl
439 7d3f4253 Iustin Pop
    in do
440 7d3f4253 Iustin Pop
      new_p <- Node.addPri tgt_p inst
441 7d3f4253 Iustin Pop
      new_s <- Node.addSec tgt_s inst new_pdx
442 7d3f4253 Iustin Pop
      let new_inst = Instance.setBoth inst new_pdx new_sdx
443 7d3f4253 Iustin Pop
          new_nl = Container.addTwo new_pdx new_p new_sdx new_s nl
444 7d3f4253 Iustin Pop
      return (new_nl, new_inst, [new_p, new_s], compCV new_nl)
445 4a340313 Iustin Pop
446 9188aeef Iustin Pop
-- | Tries to perform an instance move and returns the best table
447 9188aeef Iustin Pop
-- between the original one and the new one.
448 e4f08c46 Iustin Pop
checkSingleStep :: Table -- ^ The original table
449 e4f08c46 Iustin Pop
                -> Instance.Instance -- ^ The instance to move
450 e4f08c46 Iustin Pop
                -> Table -- ^ The current best table
451 e4f08c46 Iustin Pop
                -> IMove -- ^ The move to apply
452 e4f08c46 Iustin Pop
                -> Table -- ^ The final best table
453 e4f08c46 Iustin Pop
checkSingleStep ini_tbl target cur_tbl move =
454 e4f08c46 Iustin Pop
    let
455 e4f08c46 Iustin Pop
        Table ini_nl ini_il _ ini_plc = ini_tbl
456 8880d889 Iustin Pop
        tmp_resu = applyMove ini_nl target move
457 e4f08c46 Iustin Pop
    in
458 8880d889 Iustin Pop
      case tmp_resu of
459 f2280553 Iustin Pop
        OpFail _ -> cur_tbl
460 3173c987 Iustin Pop
        OpGood (upd_nl, new_inst, pri_idx, sec_idx) ->
461 f2280553 Iustin Pop
            let tgt_idx = Instance.idx target
462 f2280553 Iustin Pop
                upd_cvar = compCV upd_nl
463 f2280553 Iustin Pop
                upd_il = Container.add tgt_idx new_inst ini_il
464 3173c987 Iustin Pop
                upd_plc = (tgt_idx, pri_idx, sec_idx, move, upd_cvar):ini_plc
465 f2280553 Iustin Pop
                upd_tbl = Table upd_nl upd_il upd_cvar upd_plc
466 f2280553 Iustin Pop
            in
467 f2280553 Iustin Pop
              compareTables cur_tbl upd_tbl
468 e4f08c46 Iustin Pop
469 c0501c69 Iustin Pop
-- | Given the status of the current secondary as a valid new node and
470 c0501c69 Iustin Pop
-- the current candidate target node, generate the possible moves for
471 c0501c69 Iustin Pop
-- a instance.
472 c0501c69 Iustin Pop
possibleMoves :: Bool      -- ^ Whether the secondary node is a valid new node
473 e08424a8 Guido Trotter
              -> Bool      -- ^ Whether we can change the primary node
474 c0501c69 Iustin Pop
              -> Ndx       -- ^ Target node candidate
475 c0501c69 Iustin Pop
              -> [IMove]   -- ^ List of valid result moves
476 e08424a8 Guido Trotter
477 e08424a8 Guido Trotter
possibleMoves _ False tdx =
478 e08424a8 Guido Trotter
    [ReplaceSecondary tdx]
479 e08424a8 Guido Trotter
480 e08424a8 Guido Trotter
possibleMoves True True tdx =
481 40d4eba0 Iustin Pop
    [ReplaceSecondary tdx,
482 40d4eba0 Iustin Pop
     ReplaceAndFailover tdx,
483 40d4eba0 Iustin Pop
     ReplacePrimary tdx,
484 40d4eba0 Iustin Pop
     FailoverAndReplace tdx]
485 40d4eba0 Iustin Pop
486 e08424a8 Guido Trotter
possibleMoves False True tdx =
487 40d4eba0 Iustin Pop
    [ReplaceSecondary tdx,
488 40d4eba0 Iustin Pop
     ReplaceAndFailover tdx]
489 40d4eba0 Iustin Pop
490 40d4eba0 Iustin Pop
-- | Compute the best move for a given instance.
491 c0501c69 Iustin Pop
checkInstanceMove :: [Ndx]             -- ^ Allowed target node indices
492 c0501c69 Iustin Pop
                  -> Bool              -- ^ Whether disk moves are allowed
493 e08424a8 Guido Trotter
                  -> Bool              -- ^ Whether instance moves are allowed
494 c0501c69 Iustin Pop
                  -> Table             -- ^ Original table
495 c0501c69 Iustin Pop
                  -> Instance.Instance -- ^ Instance to move
496 c0501c69 Iustin Pop
                  -> Table             -- ^ Best new table for this instance
497 e08424a8 Guido Trotter
checkInstanceMove nodes_idx disk_moves inst_moves ini_tbl target =
498 4e25d1c2 Iustin Pop
    let
499 2060348b Iustin Pop
        opdx = Instance.pNode target
500 2060348b Iustin Pop
        osdx = Instance.sNode target
501 9dc6023f Iustin Pop
        nodes = filter (\idx -> idx /= opdx && idx /= osdx) nodes_idx
502 e08424a8 Guido Trotter
        use_secondary = elem osdx nodes_idx && inst_moves
503 40d4eba0 Iustin Pop
        aft_failover = if use_secondary -- if allowed to failover
504 40d4eba0 Iustin Pop
                       then checkSingleStep ini_tbl target ini_tbl Failover
505 40d4eba0 Iustin Pop
                       else ini_tbl
506 c0501c69 Iustin Pop
        all_moves = if disk_moves
507 e08424a8 Guido Trotter
                    then concatMap
508 e08424a8 Guido Trotter
                         (possibleMoves use_secondary inst_moves) nodes
509 c0501c69 Iustin Pop
                    else []
510 4e25d1c2 Iustin Pop
    in
511 4e25d1c2 Iustin Pop
      -- iterate over the possible nodes for this instance
512 9dc6023f Iustin Pop
      foldl' (checkSingleStep ini_tbl target) aft_failover all_moves
513 4e25d1c2 Iustin Pop
514 e4f08c46 Iustin Pop
-- | Compute the best next move.
515 608efcce Iustin Pop
checkMove :: [Ndx]               -- ^ Allowed target node indices
516 c0501c69 Iustin Pop
          -> Bool                -- ^ Whether disk moves are allowed
517 e08424a8 Guido Trotter
          -> Bool                -- ^ Whether instance moves are allowed
518 256810de Iustin Pop
          -> Table               -- ^ The current solution
519 e4f08c46 Iustin Pop
          -> [Instance.Instance] -- ^ List of instances still to move
520 256810de Iustin Pop
          -> Table               -- ^ The new solution
521 e08424a8 Guido Trotter
checkMove nodes_idx disk_moves inst_moves ini_tbl victims =
522 4e25d1c2 Iustin Pop
    let Table _ _ _ ini_plc = ini_tbl
523 a3eee4ad Iustin Pop
        -- we're using rwhnf from the Control.Parallel.Strategies
524 a3eee4ad Iustin Pop
        -- package; we don't need to use rnf as that would force too
525 a3eee4ad Iustin Pop
        -- much evaluation in single-threaded cases, and in
526 a3eee4ad Iustin Pop
        -- multi-threaded case the weak head normal form is enough to
527 a3eee4ad Iustin Pop
        -- spark the evaluation
528 e08424a8 Guido Trotter
        tables = parMap rwhnf (checkInstanceMove nodes_idx disk_moves
529 e08424a8 Guido Trotter
                               inst_moves ini_tbl)
530 a3eee4ad Iustin Pop
                 victims
531 4e25d1c2 Iustin Pop
        -- iterate over all instances, computing the best move
532 2a8e2dc9 Iustin Pop
        best_tbl = foldl' compareTables ini_tbl tables
533 aaaa0e43 Iustin Pop
        Table _ _ _ best_plc = best_tbl
534 a804261a Iustin Pop
    in if length best_plc == length ini_plc
535 a804261a Iustin Pop
       then ini_tbl -- no advancement
536 a804261a Iustin Pop
       else best_tbl
537 e4f08c46 Iustin Pop
538 525bfb36 Iustin Pop
-- | Check if we are allowed to go deeper in the balancing.
539 3fea6959 Iustin Pop
doNextBalance :: Table     -- ^ The starting table
540 3fea6959 Iustin Pop
              -> Int       -- ^ Remaining length
541 3fea6959 Iustin Pop
              -> Score     -- ^ Score at which to stop
542 3fea6959 Iustin Pop
              -> Bool      -- ^ The resulting table and commands
543 5ad86777 Iustin Pop
doNextBalance ini_tbl max_rounds min_score =
544 5ad86777 Iustin Pop
    let Table _ _ ini_cv ini_plc = ini_tbl
545 5ad86777 Iustin Pop
        ini_plc_len = length ini_plc
546 5ad86777 Iustin Pop
    in (max_rounds < 0 || ini_plc_len < max_rounds) && ini_cv > min_score
547 5ad86777 Iustin Pop
548 525bfb36 Iustin Pop
-- | Run a balance move.
549 f25e5aac Iustin Pop
tryBalance :: Table       -- ^ The starting table
550 f25e5aac Iustin Pop
           -> Bool        -- ^ Allow disk moves
551 e08424a8 Guido Trotter
           -> Bool        -- ^ Allow instance moves
552 2e28ac32 Iustin Pop
           -> Bool        -- ^ Only evacuate moves
553 848b65c9 Iustin Pop
           -> Score       -- ^ Min gain threshold
554 848b65c9 Iustin Pop
           -> Score       -- ^ Min gain
555 f25e5aac Iustin Pop
           -> Maybe Table -- ^ The resulting table and commands
556 e08424a8 Guido Trotter
tryBalance ini_tbl disk_moves inst_moves evac_mode mg_limit min_gain =
557 5ad86777 Iustin Pop
    let Table ini_nl ini_il ini_cv _ = ini_tbl
558 5ad86777 Iustin Pop
        all_inst = Container.elems ini_il
559 2e28ac32 Iustin Pop
        all_inst' = if evac_mode
560 2e28ac32 Iustin Pop
                    then let bad_nodes = map Node.idx . filter Node.offline $
561 2e28ac32 Iustin Pop
                                         Container.elems ini_nl
562 2e28ac32 Iustin Pop
                         in filter (\e -> Instance.sNode e `elem` bad_nodes ||
563 2e28ac32 Iustin Pop
                                          Instance.pNode e `elem` bad_nodes)
564 2e28ac32 Iustin Pop
                            all_inst
565 2e28ac32 Iustin Pop
                    else all_inst
566 c424cdc8 Iustin Pop
        reloc_inst = filter Instance.movable all_inst'
567 5ad86777 Iustin Pop
        node_idx = map Node.idx . filter (not . Node.offline) $
568 5ad86777 Iustin Pop
                   Container.elems ini_nl
569 e08424a8 Guido Trotter
        fin_tbl = checkMove node_idx disk_moves inst_moves ini_tbl reloc_inst
570 5ad86777 Iustin Pop
        (Table _ _ fin_cv _) = fin_tbl
571 f25e5aac Iustin Pop
    in
572 848b65c9 Iustin Pop
      if fin_cv < ini_cv && (ini_cv > mg_limit || ini_cv - fin_cv >= min_gain)
573 5ad86777 Iustin Pop
      then Just fin_tbl -- this round made success, return the new table
574 f25e5aac Iustin Pop
      else Nothing
575 f25e5aac Iustin Pop
576 478df686 Iustin Pop
-- * Allocation functions
577 478df686 Iustin Pop
578 525bfb36 Iustin Pop
-- | Build failure stats out of a list of failures.
579 478df686 Iustin Pop
collapseFailures :: [FailMode] -> FailStats
580 478df686 Iustin Pop
collapseFailures flst =
581 5182e970 Iustin Pop
    map (\k -> (k, length $ filter (k ==) flst)) [minBound..maxBound]
582 478df686 Iustin Pop
583 478df686 Iustin Pop
-- | Update current Allocation solution and failure stats with new
584 525bfb36 Iustin Pop
-- elements.
585 1fe81531 Iustin Pop
concatAllocs :: AllocSolution -> OpResult Node.AllocElement -> AllocSolution
586 85d0ddc3 Iustin Pop
concatAllocs as (OpFail reason) = as { asFailures = reason : asFailures as }
587 478df686 Iustin Pop
588 85d0ddc3 Iustin Pop
concatAllocs as (OpGood ns@(_, _, _, nscore)) =
589 7d3f4253 Iustin Pop
    let -- Choose the old or new solution, based on the cluster score
590 85d0ddc3 Iustin Pop
        cntok = asAllocs as
591 85d0ddc3 Iustin Pop
        osols = asSolutions as
592 478df686 Iustin Pop
        nsols = case osols of
593 a334d536 Iustin Pop
                  [] -> [ns]
594 a334d536 Iustin Pop
                  (_, _, _, oscore):[] ->
595 478df686 Iustin Pop
                      if oscore < nscore
596 478df686 Iustin Pop
                      then osols
597 a334d536 Iustin Pop
                      else [ns]
598 23f9ab76 Iustin Pop
                  -- FIXME: here we simply concat to lists with more
599 23f9ab76 Iustin Pop
                  -- than one element; we should instead abort, since
600 23f9ab76 Iustin Pop
                  -- this is not a valid usage of this function
601 a334d536 Iustin Pop
                  xs -> ns:xs
602 fbb95f28 Iustin Pop
        nsuc = cntok + 1
603 478df686 Iustin Pop
    -- Note: we force evaluation of nsols here in order to keep the
604 478df686 Iustin Pop
    -- memory profile low - we know that we will need nsols for sure
605 478df686 Iustin Pop
    -- in the next cycle, so we force evaluation of nsols, since the
606 478df686 Iustin Pop
    -- foldl' in the caller will only evaluate the tuple, but not the
607 7d11799b Iustin Pop
    -- elements of the tuple
608 85d0ddc3 Iustin Pop
    in nsols `seq` nsuc `seq` as { asAllocs = nsuc, asSolutions = nsols }
609 dbba5246 Iustin Pop
610 1bc47d38 Iustin Pop
-- | Sums two allocation solutions (e.g. for two separate node groups).
611 1bc47d38 Iustin Pop
sumAllocs :: AllocSolution -> AllocSolution -> AllocSolution
612 1bc47d38 Iustin Pop
sumAllocs (AllocSolution af aa as al) (AllocSolution bf ba bs bl) =
613 1bc47d38 Iustin Pop
    AllocSolution (af ++ bf) (aa + ba) (as ++ bs) (al ++ bl)
614 1bc47d38 Iustin Pop
615 525bfb36 Iustin Pop
-- | Given a solution, generates a reasonable description for it.
616 859fc11d Iustin Pop
describeSolution :: AllocSolution -> String
617 859fc11d Iustin Pop
describeSolution as =
618 859fc11d Iustin Pop
  let fcnt = asFailures as
619 859fc11d Iustin Pop
      sols = asSolutions as
620 859fc11d Iustin Pop
      freasons =
621 859fc11d Iustin Pop
        intercalate ", " . map (\(a, b) -> printf "%s: %d" (show a) b) .
622 859fc11d Iustin Pop
        filter ((> 0) . snd) . collapseFailures $ fcnt
623 859fc11d Iustin Pop
  in if null sols
624 859fc11d Iustin Pop
     then "No valid allocation solutions, failure reasons: " ++
625 859fc11d Iustin Pop
          (if null fcnt
626 859fc11d Iustin Pop
           then "unknown reasons"
627 859fc11d Iustin Pop
           else freasons)
628 859fc11d Iustin Pop
     else let (_, _, nodes, cv) = head sols
629 859fc11d Iustin Pop
          in printf ("score: %.8f, successes %d, failures %d (%s)" ++
630 859fc11d Iustin Pop
                     " for node(s) %s") cv (asAllocs as) (length fcnt) freasons
631 859fc11d Iustin Pop
             (intercalate "/" . map Node.name $ nodes)
632 859fc11d Iustin Pop
633 525bfb36 Iustin Pop
-- | Annotates a solution with the appropriate string.
634 859fc11d Iustin Pop
annotateSolution :: AllocSolution -> AllocSolution
635 859fc11d Iustin Pop
annotateSolution as = as { asLog = describeSolution as : asLog as }
636 859fc11d Iustin Pop
637 6cb1649f Iustin Pop
-- | Generate the valid node allocation singles or pairs for a new instance.
638 6d0bc5ca Iustin Pop
genAllocNodes :: Group.List        -- ^ Group list
639 6d0bc5ca Iustin Pop
              -> Node.List         -- ^ The node map
640 6cb1649f Iustin Pop
              -> Int               -- ^ The number of nodes required
641 6d0bc5ca Iustin Pop
              -> Bool              -- ^ Whether to drop or not
642 6d0bc5ca Iustin Pop
                                   -- unallocable nodes
643 6cb1649f Iustin Pop
              -> Result AllocNodes -- ^ The (monadic) result
644 6d0bc5ca Iustin Pop
genAllocNodes gl nl count drop_unalloc =
645 6d0bc5ca Iustin Pop
    let filter_fn = if drop_unalloc
646 e4491427 Iustin Pop
                    then filter (Group.isAllocable .
647 e4491427 Iustin Pop
                                 flip Container.find gl . Node.group)
648 6d0bc5ca Iustin Pop
                    else id
649 6d0bc5ca Iustin Pop
        all_nodes = filter_fn $ getOnline nl
650 6cb1649f Iustin Pop
        all_pairs = liftM2 (,) all_nodes all_nodes
651 6cb1649f Iustin Pop
        ok_pairs = filter (\(x, y) -> Node.idx x /= Node.idx y &&
652 6cb1649f Iustin Pop
                                      Node.group x == Node.group y) all_pairs
653 6cb1649f Iustin Pop
    in case count of
654 0d66ea67 Iustin Pop
         1 -> Ok (Left (map Node.idx all_nodes))
655 0d66ea67 Iustin Pop
         2 -> Ok (Right (map (\(p, s) -> (Node.idx p, Node.idx s)) ok_pairs))
656 6cb1649f Iustin Pop
         _ -> Bad "Unsupported number of nodes, only one or two  supported"
657 6cb1649f Iustin Pop
658 dbba5246 Iustin Pop
-- | Try to allocate an instance on the cluster.
659 dbba5246 Iustin Pop
tryAlloc :: (Monad m) =>
660 dbba5246 Iustin Pop
            Node.List         -- ^ The node list
661 dbba5246 Iustin Pop
         -> Instance.List     -- ^ The instance list
662 dbba5246 Iustin Pop
         -> Instance.Instance -- ^ The instance to allocate
663 6cb1649f Iustin Pop
         -> AllocNodes        -- ^ The allocation targets
664 78694255 Iustin Pop
         -> m AllocSolution   -- ^ Possible solution list
665 6cb1649f Iustin Pop
tryAlloc nl _ inst (Right ok_pairs) =
666 6cb1649f Iustin Pop
    let sols = foldl' (\cstate (p, s) ->
667 478df686 Iustin Pop
                           concatAllocs cstate $ allocateOnPair nl inst p s
668 85d0ddc3 Iustin Pop
                      ) emptySolution ok_pairs
669 859fc11d Iustin Pop
670 dec88196 Iustin Pop
    in if null ok_pairs -- means we have just one node
671 dec88196 Iustin Pop
       then fail "Not enough online nodes"
672 dec88196 Iustin Pop
       else return $ annotateSolution sols
673 dbba5246 Iustin Pop
674 6cb1649f Iustin Pop
tryAlloc nl _ inst (Left all_nodes) =
675 6cb1649f Iustin Pop
    let sols = foldl' (\cstate ->
676 2485487d Iustin Pop
                           concatAllocs cstate . allocateOnSingle nl inst
677 85d0ddc3 Iustin Pop
                      ) emptySolution all_nodes
678 dec88196 Iustin Pop
    in if null all_nodes
679 dec88196 Iustin Pop
       then fail "No online nodes"
680 dec88196 Iustin Pop
       else return $ annotateSolution sols
681 dbba5246 Iustin Pop
682 525bfb36 Iustin Pop
-- | Given a group/result, describe it as a nice (list of) messages.
683 aec636b9 Iustin Pop
solutionDescription :: Group.List -> (Gdx, Result AllocSolution) -> [String]
684 aec636b9 Iustin Pop
solutionDescription gl (groupId, result) =
685 9b1584fc Iustin Pop
  case result of
686 73206d0a Iustin Pop
    Ok solution -> map (printf "Group %s (%s): %s" gname pol) (asLog solution)
687 aec636b9 Iustin Pop
    Bad message -> [printf "Group %s: error %s" gname message]
688 73206d0a Iustin Pop
  where grp = Container.find groupId gl
689 73206d0a Iustin Pop
        gname = Group.name grp
690 73206d0a Iustin Pop
        pol = apolToString (Group.allocPolicy grp)
691 9b1584fc Iustin Pop
692 9b1584fc Iustin Pop
-- | From a list of possibly bad and possibly empty solutions, filter
693 88253d03 Iustin Pop
-- only the groups with a valid result. Note that the result will be
694 525bfb36 Iustin Pop
-- reversed compared to the original list.
695 73206d0a Iustin Pop
filterMGResults :: Group.List
696 73206d0a Iustin Pop
                -> [(Gdx, Result AllocSolution)]
697 73206d0a Iustin Pop
                -> [(Gdx, AllocSolution)]
698 88253d03 Iustin Pop
filterMGResults gl = foldl' fn []
699 88253d03 Iustin Pop
    where unallocable = not . Group.isAllocable . flip Container.find gl
700 88253d03 Iustin Pop
          fn accu (gdx, rasol) =
701 88253d03 Iustin Pop
              case rasol of
702 88253d03 Iustin Pop
                Bad _ -> accu
703 88253d03 Iustin Pop
                Ok sol | null (asSolutions sol) -> accu
704 88253d03 Iustin Pop
                       | unallocable gdx -> accu
705 88253d03 Iustin Pop
                       | otherwise -> (gdx, sol):accu
706 9b1584fc Iustin Pop
707 525bfb36 Iustin Pop
-- | Sort multigroup results based on policy and score.
708 73206d0a Iustin Pop
sortMGResults :: Group.List
709 73206d0a Iustin Pop
             -> [(Gdx, AllocSolution)]
710 73206d0a Iustin Pop
             -> [(Gdx, AllocSolution)]
711 73206d0a Iustin Pop
sortMGResults gl sols =
712 2a8e2dc9 Iustin Pop
    let extractScore (_, _, _, x) = x
713 73206d0a Iustin Pop
        solScore (gdx, sol) = (Group.allocPolicy (Container.find gdx gl),
714 73206d0a Iustin Pop
                               (extractScore . head . asSolutions) sol)
715 73206d0a Iustin Pop
    in sortBy (comparing solScore) sols
716 73206d0a Iustin Pop
717 9b1584fc Iustin Pop
-- | Try to allocate an instance on a multi-group cluster.
718 aec636b9 Iustin Pop
tryMGAlloc :: Group.List           -- ^ The group list
719 aec636b9 Iustin Pop
           -> Node.List            -- ^ The node list
720 aec636b9 Iustin Pop
           -> Instance.List        -- ^ The instance list
721 aec636b9 Iustin Pop
           -> Instance.Instance    -- ^ The instance to allocate
722 aec636b9 Iustin Pop
           -> Int                  -- ^ Required number of nodes
723 aec636b9 Iustin Pop
           -> Result AllocSolution -- ^ Possible solution list
724 aec636b9 Iustin Pop
tryMGAlloc mggl mgnl mgil inst cnt =
725 9b1584fc Iustin Pop
  let groups = splitCluster mgnl mgil
726 9b1584fc Iustin Pop
      sols = map (\(gid, (nl, il)) ->
727 6d0bc5ca Iustin Pop
                   (gid, genAllocNodes mggl nl cnt False >>=
728 6d0bc5ca Iustin Pop
                       tryAlloc nl il inst))
729 6cb1649f Iustin Pop
             groups::[(Gdx, Result AllocSolution)]
730 aec636b9 Iustin Pop
      all_msgs = concatMap (solutionDescription mggl) sols
731 73206d0a Iustin Pop
      goodSols = filterMGResults mggl sols
732 73206d0a Iustin Pop
      sortedSols = sortMGResults mggl goodSols
733 9b1584fc Iustin Pop
  in if null sortedSols
734 9b1584fc Iustin Pop
     then Bad $ intercalate ", " all_msgs
735 9b1584fc Iustin Pop
     else let (final_group, final_sol) = head sortedSols
736 aec636b9 Iustin Pop
              final_name = Group.name $ Container.find final_group mggl
737 aec636b9 Iustin Pop
              selmsg = "Selected group: " ++  final_name
738 9b1584fc Iustin Pop
          in Ok $ final_sol { asLog = selmsg:all_msgs }
739 9b1584fc Iustin Pop
740 9b1584fc Iustin Pop
-- | Try to relocate an instance on the cluster.
741 dbba5246 Iustin Pop
tryReloc :: (Monad m) =>
742 78694255 Iustin Pop
            Node.List       -- ^ The node list
743 78694255 Iustin Pop
         -> Instance.List   -- ^ The instance list
744 78694255 Iustin Pop
         -> Idx             -- ^ The index of the instance to move
745 478df686 Iustin Pop
         -> Int             -- ^ The number of nodes required
746 78694255 Iustin Pop
         -> [Ndx]           -- ^ Nodes which should not be used
747 78694255 Iustin Pop
         -> m AllocSolution -- ^ Solution list
748 dbba5246 Iustin Pop
tryReloc nl il xid 1 ex_idx =
749 dbba5246 Iustin Pop
    let all_nodes = getOnline nl
750 dbba5246 Iustin Pop
        inst = Container.find xid il
751 2060348b Iustin Pop
        ex_idx' = Instance.pNode inst:ex_idx
752 dbba5246 Iustin Pop
        valid_nodes = filter (not . flip elem ex_idx' . Node.idx) all_nodes
753 dbba5246 Iustin Pop
        valid_idxes = map Node.idx valid_nodes
754 478df686 Iustin Pop
        sols1 = foldl' (\cstate x ->
755 fbb95f28 Iustin Pop
                            let em = do
756 478df686 Iustin Pop
                                  (mnl, i, _, _) <-
757 478df686 Iustin Pop
                                      applyMove nl inst (ReplaceSecondary x)
758 7d3f4253 Iustin Pop
                                  return (mnl, i, [Container.find x mnl],
759 7d3f4253 Iustin Pop
                                          compCV mnl)
760 fbb95f28 Iustin Pop
                            in concatAllocs cstate em
761 85d0ddc3 Iustin Pop
                       ) emptySolution valid_idxes
762 dbba5246 Iustin Pop
    in return sols1
763 dbba5246 Iustin Pop
764 dbba5246 Iustin Pop
tryReloc _ _ _ reqn _  = fail $ "Unsupported number of relocation \
765 9f6dcdea Iustin Pop
                                \destinations required (" ++ show reqn ++
766 dbba5246 Iustin Pop
                                                  "), only one supported"
767 e4f08c46 Iustin Pop
768 4bc33d60 Iustin Pop
tryMGReloc :: (Monad m) =>
769 4bc33d60 Iustin Pop
              Group.List      -- ^ The group list
770 4bc33d60 Iustin Pop
           -> Node.List       -- ^ The node list
771 4bc33d60 Iustin Pop
           -> Instance.List   -- ^ The instance list
772 4bc33d60 Iustin Pop
           -> Idx             -- ^ The index of the instance to move
773 4bc33d60 Iustin Pop
           -> Int             -- ^ The number of nodes required
774 4bc33d60 Iustin Pop
           -> [Ndx]           -- ^ Nodes which should not be used
775 4bc33d60 Iustin Pop
           -> m AllocSolution -- ^ Solution list
776 4bc33d60 Iustin Pop
tryMGReloc _ mgnl mgil xid ncount ex_ndx = do
777 4bc33d60 Iustin Pop
  let groups = splitCluster mgnl mgil
778 4bc33d60 Iustin Pop
      -- TODO: we only relocate inside the group for now
779 4bc33d60 Iustin Pop
      inst = Container.find xid mgil
780 4bc33d60 Iustin Pop
  (nl, il) <- case lookup (instancePriGroup mgnl inst) groups of
781 4bc33d60 Iustin Pop
                Nothing -> fail $ "Cannot find group for instance " ++
782 4bc33d60 Iustin Pop
                           Instance.name inst
783 4bc33d60 Iustin Pop
                Just v -> return v
784 4bc33d60 Iustin Pop
  tryReloc nl il xid ncount ex_ndx
785 4bc33d60 Iustin Pop
786 525bfb36 Iustin Pop
-- | Change an instance's secondary node.
787 2ca68e2b Iustin Pop
evacInstance :: (Monad m) =>
788 2ca68e2b Iustin Pop
                [Ndx]                      -- ^ Excluded nodes
789 2ca68e2b Iustin Pop
             -> Instance.List              -- ^ The current instance list
790 2ca68e2b Iustin Pop
             -> (Node.List, AllocSolution) -- ^ The current state
791 2ca68e2b Iustin Pop
             -> Idx                        -- ^ The instance to evacuate
792 2ca68e2b Iustin Pop
             -> m (Node.List, AllocSolution)
793 2ca68e2b Iustin Pop
evacInstance ex_ndx il (nl, old_as) idx = do
794 2ca68e2b Iustin Pop
  -- FIXME: hardcoded one node here
795 2ca68e2b Iustin Pop
796 2ca68e2b Iustin Pop
  -- Longer explanation: evacuation is currently hardcoded to DRBD
797 2ca68e2b Iustin Pop
  -- instances (which have one secondary); hence, even if the
798 2ca68e2b Iustin Pop
  -- IAllocator protocol can request N nodes for an instance, and all
799 2ca68e2b Iustin Pop
  -- the message parsing/loading pass this, this implementation only
800 2ca68e2b Iustin Pop
  -- supports one; this situation needs to be revisited if we ever
801 2ca68e2b Iustin Pop
  -- support more than one secondary, or if we change the storage
802 2ca68e2b Iustin Pop
  -- model
803 2ca68e2b Iustin Pop
  new_as <- tryReloc nl il idx 1 ex_ndx
804 2ca68e2b Iustin Pop
  case asSolutions new_as of
805 2ca68e2b Iustin Pop
    -- an individual relocation succeeded, we kind of compose the data
806 2ca68e2b Iustin Pop
    -- from the two solutions
807 2ca68e2b Iustin Pop
    csol@(nl', _, _, _):_ ->
808 2ca68e2b Iustin Pop
        return (nl', new_as { asSolutions = csol:asSolutions old_as })
809 2ca68e2b Iustin Pop
    -- this relocation failed, so we fail the entire evac
810 2ca68e2b Iustin Pop
    _ -> fail $ "Can't evacuate instance " ++
811 2ca68e2b Iustin Pop
         Instance.name (Container.find idx il) ++
812 2ca68e2b Iustin Pop
             ": " ++ describeSolution new_as
813 2ca68e2b Iustin Pop
814 3fea6959 Iustin Pop
-- | Try to evacuate a list of nodes.
815 12b0511d Iustin Pop
tryEvac :: (Monad m) =>
816 12b0511d Iustin Pop
            Node.List       -- ^ The node list
817 12b0511d Iustin Pop
         -> Instance.List   -- ^ The instance list
818 1bc47d38 Iustin Pop
         -> [Idx]           -- ^ Instances to be evacuated
819 1bc47d38 Iustin Pop
         -> [Ndx]           -- ^ Restricted nodes (the ones being evacuated)
820 12b0511d Iustin Pop
         -> m AllocSolution -- ^ Solution list
821 1bc47d38 Iustin Pop
tryEvac nl il idxs ex_ndx = do
822 1bc47d38 Iustin Pop
  (_, sol) <- foldM (evacInstance ex_ndx il) (nl, emptySolution) idxs
823 1bc47d38 Iustin Pop
  return sol
824 1bc47d38 Iustin Pop
825 1bc47d38 Iustin Pop
-- | Multi-group evacuation of a list of nodes.
826 1bc47d38 Iustin Pop
tryMGEvac :: (Monad m) =>
827 1bc47d38 Iustin Pop
             Group.List -- ^ The group list
828 1bc47d38 Iustin Pop
          -> Node.List       -- ^ The node list
829 1bc47d38 Iustin Pop
          -> Instance.List   -- ^ The instance list
830 1bc47d38 Iustin Pop
          -> [Ndx]           -- ^ Nodes to be evacuated
831 1bc47d38 Iustin Pop
          -> m AllocSolution -- ^ Solution list
832 1bc47d38 Iustin Pop
tryMGEvac _ nl il ex_ndx =
833 5182e970 Iustin Pop
    let ex_nodes = map (`Container.find` nl) ex_ndx
834 5182e970 Iustin Pop
        all_insts = nub . concatMap Node.sList $ ex_nodes
835 1bc47d38 Iustin Pop
        gni = splitCluster nl il
836 1bc47d38 Iustin Pop
        -- we run the instance index list through a couple of maps to
837 1bc47d38 Iustin Pop
        -- get finally to a structure of the type [(group index,
838 1bc47d38 Iustin Pop
        -- [instance indices])]
839 1bc47d38 Iustin Pop
        all_insts' = map (\idx ->
840 1bc47d38 Iustin Pop
                              (instancePriGroup nl (Container.find idx il),
841 1bc47d38 Iustin Pop
                               idx)) all_insts
842 1bc47d38 Iustin Pop
        all_insts'' = groupBy ((==) `on` fst) all_insts'
843 1bc47d38 Iustin Pop
        all_insts3 = map (\xs -> let (gdxs, idxs) = unzip xs
844 1bc47d38 Iustin Pop
                                 in (head gdxs, idxs)) all_insts''
845 12b0511d Iustin Pop
    in do
846 1bc47d38 Iustin Pop
      -- that done, we now add the per-group nl/il to the tuple
847 1bc47d38 Iustin Pop
      all_insts4 <-
848 d5072e4c Iustin Pop
          mapM (\(gdx, idxs) ->
849 d5072e4c Iustin Pop
                case lookup gdx gni of
850 1bc47d38 Iustin Pop
                    Nothing -> fail $ "Can't find group index " ++ show gdx
851 1bc47d38 Iustin Pop
                    Just (gnl, gil) -> return (gdx, gnl, gil, idxs))
852 1bc47d38 Iustin Pop
          all_insts3
853 1bc47d38 Iustin Pop
      results <- mapM (\(_, gnl, gil, idxs) -> tryEvac gnl gil idxs ex_ndx)
854 1bc47d38 Iustin Pop
                 all_insts4
855 2a8e2dc9 Iustin Pop
      let sol = foldl' sumAllocs emptySolution results
856 859fc11d Iustin Pop
      return $ annotateSolution sol
857 12b0511d Iustin Pop
858 525bfb36 Iustin Pop
-- | Recursively place instances on the cluster until we're out of space.
859 3ce8009a Iustin Pop
iterateAlloc :: Node.List
860 3ce8009a Iustin Pop
             -> Instance.List
861 3ce8009a Iustin Pop
             -> Instance.Instance
862 41b5c85a Iustin Pop
             -> AllocNodes
863 3ce8009a Iustin Pop
             -> [Instance.Instance]
864 d5ccec02 Iustin Pop
             -> [CStats]
865 40ee14bc Iustin Pop
             -> Result AllocResult
866 41b5c85a Iustin Pop
iterateAlloc nl il newinst allocnodes ixes cstats =
867 3ce8009a Iustin Pop
      let depth = length ixes
868 3ce8009a Iustin Pop
          newname = printf "new-%d" depth::String
869 3ce8009a Iustin Pop
          newidx = length (Container.elems il) + depth
870 3ce8009a Iustin Pop
          newi2 = Instance.setIdx (Instance.setName newinst newname) newidx
871 41b5c85a Iustin Pop
      in case tryAlloc nl il newi2 allocnodes of
872 3ce8009a Iustin Pop
           Bad s -> Bad s
873 85d0ddc3 Iustin Pop
           Ok (AllocSolution { asFailures = errs, asSolutions = sols3 }) ->
874 3ce8009a Iustin Pop
               case sols3 of
875 d5ccec02 Iustin Pop
                 [] -> Ok (collapseFailures errs, nl, il, ixes, cstats)
876 a334d536 Iustin Pop
                 (xnl, xi, _, _):[] ->
877 94d08202 Iustin Pop
                     iterateAlloc xnl (Container.add newidx xi il)
878 41b5c85a Iustin Pop
                                  newinst allocnodes (xi:ixes)
879 d5ccec02 Iustin Pop
                                  (totalResources xnl:cstats)
880 3ce8009a Iustin Pop
                 _ -> Bad "Internal error: multiple solutions for single\
881 3ce8009a Iustin Pop
                          \ allocation"
882 3ce8009a Iustin Pop
883 525bfb36 Iustin Pop
-- | The core of the tiered allocation mode.
884 3ce8009a Iustin Pop
tieredAlloc :: Node.List
885 3ce8009a Iustin Pop
            -> Instance.List
886 3ce8009a Iustin Pop
            -> Instance.Instance
887 41b5c85a Iustin Pop
            -> AllocNodes
888 3ce8009a Iustin Pop
            -> [Instance.Instance]
889 d5ccec02 Iustin Pop
            -> [CStats]
890 40ee14bc Iustin Pop
            -> Result AllocResult
891 41b5c85a Iustin Pop
tieredAlloc nl il newinst allocnodes ixes cstats =
892 41b5c85a Iustin Pop
    case iterateAlloc nl il newinst allocnodes ixes cstats of
893 3ce8009a Iustin Pop
      Bad s -> Bad s
894 d5ccec02 Iustin Pop
      Ok (errs, nl', il', ixes', cstats') ->
895 3ce8009a Iustin Pop
          case Instance.shrinkByType newinst . fst . last $
896 3ce8009a Iustin Pop
               sortBy (comparing snd) errs of
897 d5ccec02 Iustin Pop
            Bad _ -> Ok (errs, nl', il', ixes', cstats')
898 3ce8009a Iustin Pop
            Ok newinst' ->
899 41b5c85a Iustin Pop
                tieredAlloc nl' il' newinst' allocnodes ixes' cstats'
900 3ce8009a Iustin Pop
901 949397c8 Iustin Pop
-- | Compute the tiered spec string description from a list of
902 949397c8 Iustin Pop
-- allocated instances.
903 949397c8 Iustin Pop
tieredSpecMap :: [Instance.Instance]
904 949397c8 Iustin Pop
              -> [String]
905 949397c8 Iustin Pop
tieredSpecMap trl_ixes =
906 949397c8 Iustin Pop
    let fin_trl_ixes = reverse trl_ixes
907 949397c8 Iustin Pop
        ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
908 949397c8 Iustin Pop
        spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
909 949397c8 Iustin Pop
                   ix_byspec
910 949397c8 Iustin Pop
    in  map (\(spec, cnt) -> printf "%d,%d,%d=%d" (rspecMem spec)
911 949397c8 Iustin Pop
                             (rspecDsk spec) (rspecCpu spec) cnt) spec_map
912 949397c8 Iustin Pop
913 9188aeef Iustin Pop
-- * Formatting functions
914 e4f08c46 Iustin Pop
915 e4f08c46 Iustin Pop
-- | Given the original and final nodes, computes the relocation description.
916 c9926b22 Iustin Pop
computeMoves :: Instance.Instance -- ^ The instance to be moved
917 c9926b22 Iustin Pop
             -> String -- ^ The instance name
918 668c03b3 Iustin Pop
             -> IMove  -- ^ The move being performed
919 e4f08c46 Iustin Pop
             -> String -- ^ New primary
920 e4f08c46 Iustin Pop
             -> String -- ^ New secondary
921 e4f08c46 Iustin Pop
             -> (String, [String])
922 e4f08c46 Iustin Pop
                -- ^ Tuple of moves and commands list; moves is containing
923 e4f08c46 Iustin Pop
                -- either @/f/@ for failover or @/r:name/@ for replace
924 e4f08c46 Iustin Pop
                -- secondary, while the command list holds gnt-instance
925 e4f08c46 Iustin Pop
                -- commands (without that prefix), e.g \"@failover instance1@\"
926 668c03b3 Iustin Pop
computeMoves i inam mv c d =
927 668c03b3 Iustin Pop
    case mv of
928 668c03b3 Iustin Pop
      Failover -> ("f", [mig])
929 668c03b3 Iustin Pop
      FailoverAndReplace _ -> (printf "f r:%s" d, [mig, rep d])
930 668c03b3 Iustin Pop
      ReplaceSecondary _ -> (printf "r:%s" d, [rep d])
931 668c03b3 Iustin Pop
      ReplaceAndFailover _ -> (printf "r:%s f" c, [rep c, mig])
932 668c03b3 Iustin Pop
      ReplacePrimary _ -> (printf "f r:%s f" c, [mig, rep c, mig])
933 c9926b22 Iustin Pop
    where morf = if Instance.running i then "migrate" else "failover"
934 c9926b22 Iustin Pop
          mig = printf "%s -f %s" morf inam::String
935 c9926b22 Iustin Pop
          rep n = printf "replace-disks -n %s %s" n inam
936 e4f08c46 Iustin Pop
937 9188aeef Iustin Pop
-- | Converts a placement to string format.
938 9188aeef Iustin Pop
printSolutionLine :: Node.List     -- ^ The node list
939 9188aeef Iustin Pop
                  -> Instance.List -- ^ The instance list
940 9188aeef Iustin Pop
                  -> Int           -- ^ Maximum node name length
941 9188aeef Iustin Pop
                  -> Int           -- ^ Maximum instance name length
942 9188aeef Iustin Pop
                  -> Placement     -- ^ The current placement
943 9188aeef Iustin Pop
                  -> Int           -- ^ The index of the placement in
944 9188aeef Iustin Pop
                                   -- the solution
945 db1bcfe8 Iustin Pop
                  -> (String, [String])
946 db1bcfe8 Iustin Pop
printSolutionLine nl il nmlen imlen plc pos =
947 ca8258d9 Iustin Pop
    let
948 ca8258d9 Iustin Pop
        pmlen = (2*nmlen + 1)
949 668c03b3 Iustin Pop
        (i, p, s, mv, c) = plc
950 ca8258d9 Iustin Pop
        inst = Container.find i il
951 14c972c7 Iustin Pop
        inam = Instance.alias inst
952 14c972c7 Iustin Pop
        npri = Node.alias $ Container.find p nl
953 14c972c7 Iustin Pop
        nsec = Node.alias $ Container.find s nl
954 14c972c7 Iustin Pop
        opri = Node.alias $ Container.find (Instance.pNode inst) nl
955 14c972c7 Iustin Pop
        osec = Node.alias $ Container.find (Instance.sNode inst) nl
956 668c03b3 Iustin Pop
        (moves, cmds) =  computeMoves inst inam mv npri nsec
957 9f6dcdea Iustin Pop
        ostr = printf "%s:%s" opri osec::String
958 9f6dcdea Iustin Pop
        nstr = printf "%s:%s" npri nsec::String
959 ca8258d9 Iustin Pop
    in
960 ab271fc1 Iustin Pop
      (printf "  %3d. %-*s %-*s => %-*s %.8f a=%s"
961 ab271fc1 Iustin Pop
       pos imlen inam pmlen ostr
962 ca8258d9 Iustin Pop
       pmlen nstr c moves,
963 ca8258d9 Iustin Pop
       cmds)
964 ca8258d9 Iustin Pop
965 0e8ae201 Iustin Pop
-- | Return the instance and involved nodes in an instance move.
966 0e8ae201 Iustin Pop
involvedNodes :: Instance.List -> Placement -> [Ndx]
967 0e8ae201 Iustin Pop
involvedNodes il plc =
968 3173c987 Iustin Pop
    let (i, np, ns, _, _) = plc
969 0e8ae201 Iustin Pop
        inst = Container.find i il
970 2060348b Iustin Pop
        op = Instance.pNode inst
971 2060348b Iustin Pop
        os = Instance.sNode inst
972 0e8ae201 Iustin Pop
    in nub [np, ns, op, os]
973 0e8ae201 Iustin Pop
974 0e8ae201 Iustin Pop
-- | Inner function for splitJobs, that either appends the next job to
975 0e8ae201 Iustin Pop
-- the current jobset, or starts a new jobset.
976 0e8ae201 Iustin Pop
mergeJobs :: ([JobSet], [Ndx]) -> MoveJob -> ([JobSet], [Ndx])
977 924f9c16 Iustin Pop
mergeJobs ([], _) n@(ndx, _, _, _) = ([[n]], ndx)
978 924f9c16 Iustin Pop
mergeJobs (cjs@(j:js), nbuf) n@(ndx, _, _, _)
979 0e8ae201 Iustin Pop
    | null (ndx `intersect` nbuf) = ((n:j):js, ndx ++ nbuf)
980 0e8ae201 Iustin Pop
    | otherwise = ([n]:cjs, ndx)
981 0e8ae201 Iustin Pop
982 0e8ae201 Iustin Pop
-- | Break a list of moves into independent groups. Note that this
983 0e8ae201 Iustin Pop
-- will reverse the order of jobs.
984 0e8ae201 Iustin Pop
splitJobs :: [MoveJob] -> [JobSet]
985 0e8ae201 Iustin Pop
splitJobs = fst . foldl mergeJobs ([], [])
986 0e8ae201 Iustin Pop
987 0e8ae201 Iustin Pop
-- | Given a list of commands, prefix them with @gnt-instance@ and
988 0e8ae201 Iustin Pop
-- also beautify the display a little.
989 0e8ae201 Iustin Pop
formatJob :: Int -> Int -> (Int, MoveJob) -> [String]
990 924f9c16 Iustin Pop
formatJob jsn jsl (sn, (_, _, _, cmds)) =
991 0e8ae201 Iustin Pop
    let out =
992 0e8ae201 Iustin Pop
            printf "  echo job %d/%d" jsn sn:
993 0e8ae201 Iustin Pop
            printf "  check":
994 0e8ae201 Iustin Pop
            map ("  gnt-instance " ++) cmds
995 0e8ae201 Iustin Pop
    in if sn == 1
996 0e8ae201 Iustin Pop
       then ["", printf "echo jobset %d, %d jobs" jsn jsl] ++ out
997 0e8ae201 Iustin Pop
       else out
998 0e8ae201 Iustin Pop
999 9188aeef Iustin Pop
-- | Given a list of commands, prefix them with @gnt-instance@ and
1000 9188aeef Iustin Pop
-- also beautify the display a little.
1001 0e8ae201 Iustin Pop
formatCmds :: [JobSet] -> String
1002 9f6dcdea Iustin Pop
formatCmds =
1003 9f6dcdea Iustin Pop
    unlines .
1004 0e8ae201 Iustin Pop
    concatMap (\(jsn, js) -> concatMap (formatJob jsn (length js))
1005 0e8ae201 Iustin Pop
                             (zip [1..] js)) .
1006 9f6dcdea Iustin Pop
    zip [1..]
1007 142538ff Iustin Pop
1008 e4f08c46 Iustin Pop
-- | Print the node list.
1009 e98fb766 Iustin Pop
printNodes :: Node.List -> [String] -> String
1010 e98fb766 Iustin Pop
printNodes nl fs =
1011 6dfa04fd Iustin Pop
    let fields = case fs of
1012 6dfa04fd Iustin Pop
          [] -> Node.defaultFields
1013 6dfa04fd Iustin Pop
          "+":rest -> Node.defaultFields ++ rest
1014 6dfa04fd Iustin Pop
          _ -> fs
1015 5182e970 Iustin Pop
        snl = sortBy (comparing Node.idx) (Container.elems nl)
1016 e98fb766 Iustin Pop
        (header, isnum) = unzip $ map Node.showHeader fields
1017 c5f7412e Iustin Pop
    in unlines . map ((:) ' ' .  intercalate " ") $
1018 e98fb766 Iustin Pop
       formatTable (header:map (Node.list fields) snl) isnum
1019 e4f08c46 Iustin Pop
1020 507fda3f Iustin Pop
-- | Print the instance list.
1021 507fda3f Iustin Pop
printInsts :: Node.List -> Instance.List -> String
1022 507fda3f Iustin Pop
printInsts nl il =
1023 5182e970 Iustin Pop
    let sil = sortBy (comparing Instance.idx) (Container.elems il)
1024 30ff0c73 Iustin Pop
        helper inst = [ if Instance.running inst then "R" else " "
1025 30ff0c73 Iustin Pop
                      , Instance.name inst
1026 30ff0c73 Iustin Pop
                      , Container.nameOf nl (Instance.pNode inst)
1027 5182e970 Iustin Pop
                      , let sdx = Instance.sNode inst
1028 5182e970 Iustin Pop
                        in if sdx == Node.noSecondary
1029 5182e970 Iustin Pop
                           then  ""
1030 5182e970 Iustin Pop
                           else Container.nameOf nl sdx
1031 93439b1c Iustin Pop
                      , if Instance.auto_balance inst then "Y" else "N"
1032 30ff0c73 Iustin Pop
                      , printf "%3d" $ Instance.vcpus inst
1033 30ff0c73 Iustin Pop
                      , printf "%5d" $ Instance.mem inst
1034 30ff0c73 Iustin Pop
                      , printf "%5d" $ Instance.dsk inst `div` 1024
1035 30ff0c73 Iustin Pop
                      , printf "%5.3f" lC
1036 30ff0c73 Iustin Pop
                      , printf "%5.3f" lM
1037 30ff0c73 Iustin Pop
                      , printf "%5.3f" lD
1038 30ff0c73 Iustin Pop
                      , printf "%5.3f" lN
1039 30ff0c73 Iustin Pop
                      ]
1040 30ff0c73 Iustin Pop
            where DynUtil lC lM lD lN = Instance.util inst
1041 93439b1c Iustin Pop
        header = [ "F", "Name", "Pri_node", "Sec_node", "Auto_bal"
1042 93439b1c Iustin Pop
                 , "vcpu", "mem" , "dsk", "lCpu", "lMem", "lDsk", "lNet" ]
1043 93439b1c Iustin Pop
        isnum = False:False:False:False:False:repeat True
1044 c5f7412e Iustin Pop
    in unlines . map ((:) ' ' . intercalate " ") $
1045 c5f7412e Iustin Pop
       formatTable (header:map helper sil) isnum
1046 507fda3f Iustin Pop
1047 9188aeef Iustin Pop
-- | Shows statistics for a given node list.
1048 262a08a2 Iustin Pop
printStats :: Node.List -> String
1049 e4f08c46 Iustin Pop
printStats nl =
1050 fca250e9 Iustin Pop
    let dcvs = compDetailedCV nl
1051 8a3b30ca Iustin Pop
        (weights, names) = unzip detailedCVInfo
1052 8a3b30ca Iustin Pop
        hd = zip3 (weights ++ repeat 1) (names ++ repeat "unknown") dcvs
1053 8a3b30ca Iustin Pop
        formatted = map (\(w, header, val) ->
1054 8a3b30ca Iustin Pop
                             printf "%s=%.8f(x%.2f)" header val w::String) hd
1055 fca250e9 Iustin Pop
    in intercalate ", " formatted
1056 6b20875c Iustin Pop
1057 6b20875c Iustin Pop
-- | Convert a placement into a list of OpCodes (basically a job).
1058 3e4480e0 Iustin Pop
iMoveToJob :: Node.List -> Instance.List
1059 6b20875c Iustin Pop
          -> Idx -> IMove -> [OpCodes.OpCode]
1060 3e4480e0 Iustin Pop
iMoveToJob nl il idx move =
1061 dfbbd43a Iustin Pop
    let inst = Container.find idx il
1062 3e4480e0 Iustin Pop
        iname = Instance.name inst
1063 3e4480e0 Iustin Pop
        lookNode  = Just . Container.nameOf nl
1064 10028866 René Nussbaumer
        opF = OpCodes.OpInstanceMigrate iname True False True
1065 10028866 René Nussbaumer
        opR n = OpCodes.OpInstanceReplaceDisks iname (lookNode n)
1066 6b20875c Iustin Pop
                OpCodes.ReplaceNewSecondary [] Nothing
1067 6b20875c Iustin Pop
    in case move of
1068 6b20875c Iustin Pop
         Failover -> [ opF ]
1069 6b20875c Iustin Pop
         ReplacePrimary np -> [ opF, opR np, opF ]
1070 6b20875c Iustin Pop
         ReplaceSecondary ns -> [ opR ns ]
1071 6b20875c Iustin Pop
         ReplaceAndFailover np -> [ opR np, opF ]
1072 6b20875c Iustin Pop
         FailoverAndReplace ns -> [ opF, opR ns ]
1073 32b8d9c0 Iustin Pop
1074 949397c8 Iustin Pop
-- * Node group functions
1075 949397c8 Iustin Pop
1076 525bfb36 Iustin Pop
-- | Computes the group of an instance.
1077 10ef6b4e Iustin Pop
instanceGroup :: Node.List -> Instance.Instance -> Result Gdx
1078 32b8d9c0 Iustin Pop
instanceGroup nl i =
1079 32b8d9c0 Iustin Pop
  let sidx = Instance.sNode i
1080 32b8d9c0 Iustin Pop
      pnode = Container.find (Instance.pNode i) nl
1081 32b8d9c0 Iustin Pop
      snode = if sidx == Node.noSecondary
1082 32b8d9c0 Iustin Pop
              then pnode
1083 32b8d9c0 Iustin Pop
              else Container.find sidx nl
1084 10ef6b4e Iustin Pop
      pgroup = Node.group pnode
1085 10ef6b4e Iustin Pop
      sgroup = Node.group snode
1086 10ef6b4e Iustin Pop
  in if pgroup /= sgroup
1087 10ef6b4e Iustin Pop
     then fail ("Instance placed accross two node groups, primary " ++
1088 10ef6b4e Iustin Pop
                show pgroup ++ ", secondary " ++ show sgroup)
1089 10ef6b4e Iustin Pop
     else return pgroup
1090 32b8d9c0 Iustin Pop
1091 525bfb36 Iustin Pop
-- | Computes the group of an instance per the primary node.
1092 4bc33d60 Iustin Pop
instancePriGroup :: Node.List -> Instance.Instance -> Gdx
1093 4bc33d60 Iustin Pop
instancePriGroup nl i =
1094 4bc33d60 Iustin Pop
  let pnode = Container.find (Instance.pNode i) nl
1095 4bc33d60 Iustin Pop
  in  Node.group pnode
1096 4bc33d60 Iustin Pop
1097 32b8d9c0 Iustin Pop
-- | Compute the list of badly allocated instances (split across node
1098 525bfb36 Iustin Pop
-- groups).
1099 32b8d9c0 Iustin Pop
findSplitInstances :: Node.List -> Instance.List -> [Instance.Instance]
1100 2a8e2dc9 Iustin Pop
findSplitInstances nl =
1101 2a8e2dc9 Iustin Pop
  filter (not . isOk . instanceGroup nl) . Container.elems
1102 f4161783 Iustin Pop
1103 525bfb36 Iustin Pop
-- | Splits a cluster into the component node groups.
1104 f4161783 Iustin Pop
splitCluster :: Node.List -> Instance.List ->
1105 10ef6b4e Iustin Pop
                [(Gdx, (Node.List, Instance.List))]
1106 f4161783 Iustin Pop
splitCluster nl il =
1107 f4161783 Iustin Pop
  let ngroups = Node.computeGroups (Container.elems nl)
1108 f4161783 Iustin Pop
  in map (\(guuid, nodes) ->
1109 f4161783 Iustin Pop
           let nidxs = map Node.idx nodes
1110 f4161783 Iustin Pop
               nodes' = zip nidxs nodes
1111 f4161783 Iustin Pop
               instances = Container.filter ((`elem` nidxs) . Instance.pNode) il
1112 cb0c77ff Iustin Pop
           in (guuid, (Container.fromList nodes', instances))) ngroups