Statistics
| Branch: | Tag: | Revision:

root / htest / Test / Ganeti / HTools / Node.hs @ 61899e64

History | View | Annotate | Download (12 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2
{-# OPTIONS_GHC -fno-warn-orphans #-}
3

    
4
{-| Unittests for ganeti-htools.
5

    
6
-}
7

    
8
{-
9

    
10
Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
11

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

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

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

    
27
-}
28

    
29
module Test.Ganeti.HTools.Node
30
  ( testHTools_Node
31
  , Node.Node(..)
32
  , setInstanceSmallerThanNode
33
  , genNode
34
  , genOnlineNode
35
  ) where
36

    
37
import Test.QuickCheck
38

    
39
import Control.Monad
40
import qualified Data.Map as Map
41
import Data.List
42

    
43
import Test.Ganeti.TestHelper
44
import Test.Ganeti.TestCommon
45
import Test.Ganeti.TestHTools
46
import Test.Ganeti.HTools.Instance (genInstanceSmallerThanNode)
47

    
48
import Ganeti.BasicTypes
49
import qualified Ganeti.HTools.Container as Container
50
import qualified Ganeti.HTools.Instance as Instance
51
import qualified Ganeti.HTools.Node as Node
52
import qualified Ganeti.HTools.Types as Types
53

    
54
-- * Arbitrary instances
55

    
56
-- | Generates an arbitrary node based on sizing information.
57
genNode :: Maybe Int -- ^ Minimum node size in terms of units
58
        -> Maybe Int -- ^ Maximum node size (when Nothing, bounded
59
                     -- just by the max... constants)
60
        -> Gen Node.Node
61
genNode min_multiplier max_multiplier = do
62
  let (base_mem, base_dsk, base_cpu) =
63
        case min_multiplier of
64
          Just mm -> (mm * Types.unitMem,
65
                      mm * Types.unitDsk,
66
                      mm * Types.unitCpu)
67
          Nothing -> (0, 0, 0)
68
      (top_mem, top_dsk, top_cpu)  =
69
        case max_multiplier of
70
          Just mm -> (mm * Types.unitMem,
71
                      mm * Types.unitDsk,
72
                      mm * Types.unitCpu)
73
          Nothing -> (maxMem, maxDsk, maxCpu)
74
  name  <- getFQDN
75
  mem_t <- choose (base_mem, top_mem)
76
  mem_f <- choose (base_mem, mem_t)
77
  mem_n <- choose (0, mem_t - mem_f)
78
  dsk_t <- choose (base_dsk, top_dsk)
79
  dsk_f <- choose (base_dsk, dsk_t)
80
  cpu_t <- choose (base_cpu, top_cpu)
81
  offl  <- arbitrary
82
  let n = Node.create name (fromIntegral mem_t) mem_n mem_f
83
          (fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) offl 1 0
84
      n' = Node.setPolicy nullIPolicy n
85
  return $ Node.buildPeers n' Container.empty
86

    
87
-- | Helper function to generate a sane node.
88
genOnlineNode :: Gen Node.Node
89
genOnlineNode =
90
  arbitrary `suchThat` (\n -> not (Node.offline n) &&
91
                              not (Node.failN1 n) &&
92
                              Node.availDisk n > 0 &&
93
                              Node.availMem n > 0 &&
94
                              Node.availCpu n > 0)
95

    
96
-- and a random node
97
instance Arbitrary Node.Node where
98
  arbitrary = genNode Nothing Nothing
99

    
100
-- * Test cases
101

    
102
prop_setAlias :: Node.Node -> String -> Bool
103
prop_setAlias node name =
104
  Node.name newnode == Node.name node &&
105
  Node.alias newnode == name
106
    where newnode = Node.setAlias node name
107

    
108
prop_setOffline :: Node.Node -> Bool -> Property
109
prop_setOffline node status =
110
  Node.offline newnode ==? status
111
    where newnode = Node.setOffline node status
112

    
113
prop_setXmem :: Node.Node -> Int -> Property
114
prop_setXmem node xm =
115
  Node.xMem newnode ==? xm
116
    where newnode = Node.setXmem node xm
117

    
118
prop_setMcpu :: Node.Node -> Double -> Property
119
prop_setMcpu node mc =
120
  Types.iPolicyVcpuRatio (Node.iPolicy newnode) ==? mc
121
    where newnode = Node.setMcpu node mc
122

    
123
prop_setFmemGreater :: Node.Node -> Int -> Property
124
prop_setFmemGreater node new_mem =
125
  not (Node.failN1 node) && (Node.rMem node >= 0) &&
126
  (new_mem > Node.rMem node) ==>
127
  not (Node.failN1 (Node.setFmem node new_mem))
128

    
129
prop_setFmemExact :: Node.Node -> Property
130
prop_setFmemExact node =
131
  not (Node.failN1 node) && (Node.rMem node >= 0) ==>
132
  not (Node.failN1 (Node.setFmem node (Node.rMem node)))
133

    
134
-- Check if adding an instance that consumes exactly all reserved
135
-- memory does not raise an N+1 error
136
prop_addPri_NoN1Fail :: Property
137
prop_addPri_NoN1Fail =
138
  forAll genOnlineNode $ \node ->
139
  forAll (genInstanceSmallerThanNode node) $ \inst ->
140
  let inst' = inst { Instance.mem = Node.fMem node - Node.rMem node }
141
  in (Node.addPri node inst' /=? Bad Types.FailN1)
142

    
143
-- | Check that an instance add with too high memory or disk will be
144
-- rejected.
145
prop_addPriFM :: Node.Node -> Instance.Instance -> Property
146
prop_addPriFM node inst =
147
  Instance.mem inst >= Node.fMem node && not (Node.failN1 node) &&
148
  not (Instance.isOffline inst) ==>
149
  (Node.addPri node inst'' ==? Bad Types.FailMem)
150
  where inst' = setInstanceSmallerThanNode node inst
151
        inst'' = inst' { Instance.mem = Instance.mem inst }
152

    
153
-- | Check that adding a primary instance with too much disk fails
154
-- with type FailDisk.
155
prop_addPriFD :: Node.Node -> Instance.Instance -> Property
156
prop_addPriFD node inst =
157
  forAll (elements Instance.localStorageTemplates) $ \dt ->
158
  Instance.dsk inst >= Node.fDsk node && not (Node.failN1 node) ==>
159
  let inst' = setInstanceSmallerThanNode node inst
160
      inst'' = inst' { Instance.dsk = Instance.dsk inst
161
                     , Instance.diskTemplate = dt }
162
  in (Node.addPri node inst'' ==? Bad Types.FailDisk)
163

    
164
-- | Check that adding a primary instance with too many VCPUs fails
165
-- with type FailCPU.
166
prop_addPriFC :: Property
167
prop_addPriFC =
168
  forAll (choose (1, maxCpu)) $ \extra ->
169
  forAll genOnlineNode $ \node ->
170
  forAll (arbitrary `suchThat` Instance.notOffline) $ \inst ->
171
  let inst' = setInstanceSmallerThanNode node inst
172
      inst'' = inst' { Instance.vcpus = Node.availCpu node + extra }
173
  in case Node.addPri node inst'' of
174
       Bad Types.FailCPU -> passTest
175
       v -> failTest $ "Expected OpFail FailCPU, but got " ++ show v
176

    
177
-- | Check that an instance add with too high memory or disk will be
178
-- rejected.
179
prop_addSec :: Node.Node -> Instance.Instance -> Int -> Property
180
prop_addSec node inst pdx =
181
  ((Instance.mem inst >= (Node.fMem node - Node.rMem node) &&
182
    not (Instance.isOffline inst)) ||
183
   Instance.dsk inst >= Node.fDsk node) &&
184
  not (Node.failN1 node) ==>
185
      isBad (Node.addSec node inst pdx)
186

    
187
-- | Check that an offline instance with reasonable disk size but
188
-- extra mem/cpu can always be added.
189
prop_addOfflinePri :: NonNegative Int -> NonNegative Int -> Property
190
prop_addOfflinePri (NonNegative extra_mem) (NonNegative extra_cpu) =
191
  forAll genOnlineNode $ \node ->
192
  forAll (genInstanceSmallerThanNode node) $ \inst ->
193
  let inst' = inst { Instance.runSt = Types.AdminOffline
194
                   , Instance.mem = Node.availMem node + extra_mem
195
                   , Instance.vcpus = Node.availCpu node + extra_cpu }
196
  in case Node.addPri node inst' of
197
       Ok _ -> passTest
198
       v -> failTest $ "Expected OpGood, but got: " ++ show v
199

    
200
-- | Check that an offline instance with reasonable disk size but
201
-- extra mem/cpu can always be added.
202
prop_addOfflineSec :: NonNegative Int -> NonNegative Int
203
                   -> Types.Ndx -> Property
204
prop_addOfflineSec (NonNegative extra_mem) (NonNegative extra_cpu) pdx =
205
  forAll genOnlineNode $ \node ->
206
  forAll (genInstanceSmallerThanNode node) $ \inst ->
207
  let inst' = inst { Instance.runSt = Types.AdminOffline
208
                   , Instance.mem = Node.availMem node + extra_mem
209
                   , Instance.vcpus = Node.availCpu node + extra_cpu
210
                   , Instance.diskTemplate = Types.DTDrbd8 }
211
  in case Node.addSec node inst' pdx of
212
       Ok _ -> passTest
213
       v -> failTest $ "Expected OpGood/OpGood, but got: " ++ show v
214

    
215
-- | Checks for memory reservation changes.
216
prop_rMem :: Instance.Instance -> Property
217
prop_rMem inst =
218
  not (Instance.isOffline inst) ==>
219
  forAll (genOnlineNode `suchThat` ((> Types.unitMem) . Node.fMem)) $ \node ->
220
  -- ab = auto_balance, nb = non-auto_balance
221
  -- we use -1 as the primary node of the instance
222
  let inst' = inst { Instance.pNode = -1, Instance.autoBalance = True
223
                   , Instance.diskTemplate = Types.DTDrbd8 }
224
      inst_ab = setInstanceSmallerThanNode node inst'
225
      inst_nb = inst_ab { Instance.autoBalance = False }
226
      -- now we have the two instances, identical except the
227
      -- autoBalance attribute
228
      orig_rmem = Node.rMem node
229
      inst_idx = Instance.idx inst_ab
230
      node_add_ab = Node.addSec node inst_ab (-1)
231
      node_add_nb = Node.addSec node inst_nb (-1)
232
      node_del_ab = liftM (`Node.removeSec` inst_ab) node_add_ab
233
      node_del_nb = liftM (`Node.removeSec` inst_nb) node_add_nb
234
  in case (node_add_ab, node_add_nb, node_del_ab, node_del_nb) of
235
       (Ok a_ab, Ok a_nb,
236
        Ok d_ab, Ok d_nb) ->
237
         printTestCase "Consistency checks failed" $
238
           Node.rMem a_ab >  orig_rmem &&
239
           Node.rMem a_ab - orig_rmem == Instance.mem inst_ab &&
240
           Node.rMem a_nb == orig_rmem &&
241
           Node.rMem d_ab == orig_rmem &&
242
           Node.rMem d_nb == orig_rmem &&
243
           -- this is not related to rMem, but as good a place to
244
           -- test as any
245
           inst_idx `elem` Node.sList a_ab &&
246
           inst_idx `notElem` Node.sList d_ab
247
       x -> failTest $ "Failed to add/remove instances: " ++ show x
248

    
249
-- | Check mdsk setting.
250
prop_setMdsk :: Node.Node -> SmallRatio -> Bool
251
prop_setMdsk node mx =
252
  Node.loDsk node' >= 0 &&
253
  fromIntegral (Node.loDsk node') <= Node.tDsk node &&
254
  Node.availDisk node' >= 0 &&
255
  Node.availDisk node' <= Node.fDsk node' &&
256
  fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
257
  Node.mDsk node' == mx'
258
    where node' = Node.setMdsk node mx'
259
          SmallRatio mx' = mx
260

    
261
-- Check tag maps
262
prop_tagMaps_idempotent :: Property
263
prop_tagMaps_idempotent =
264
  forAll genTags $ \tags ->
265
  Node.delTags (Node.addTags m tags) tags ==? m
266
    where m = Map.empty
267

    
268
prop_tagMaps_reject :: Property
269
prop_tagMaps_reject =
270
  forAll (genTags `suchThat` (not . null)) $ \tags ->
271
  let m = Node.addTags Map.empty tags
272
  in all (\t -> Node.rejectAddTags m [t]) tags
273

    
274
prop_showField :: Node.Node -> Property
275
prop_showField node =
276
  forAll (elements Node.defaultFields) $ \ field ->
277
  fst (Node.showHeader field) /= Types.unknownField &&
278
  Node.showField node field /= Types.unknownField
279

    
280
prop_computeGroups :: [Node.Node] -> Bool
281
prop_computeGroups nodes =
282
  let ng = Node.computeGroups nodes
283
      onlyuuid = map fst ng
284
  in length nodes == sum (map (length . snd) ng) &&
285
     all (\(guuid, ns) -> all ((== guuid) . Node.group) ns) ng &&
286
     length (nub onlyuuid) == length onlyuuid &&
287
     (null nodes || not (null ng))
288

    
289
-- Check idempotence of add/remove operations
290
prop_addPri_idempotent :: Property
291
prop_addPri_idempotent =
292
  forAll genOnlineNode $ \node ->
293
  forAll (genInstanceSmallerThanNode node) $ \inst ->
294
  case Node.addPri node inst of
295
    Ok node' -> Node.removePri node' inst ==? node
296
    _ -> failTest "Can't add instance"
297

    
298
prop_addSec_idempotent :: Property
299
prop_addSec_idempotent =
300
  forAll genOnlineNode $ \node ->
301
  forAll (genInstanceSmallerThanNode node) $ \inst ->
302
  let pdx = Node.idx node + 1
303
      inst' = Instance.setPri inst pdx
304
      inst'' = inst' { Instance.diskTemplate = Types.DTDrbd8 }
305
  in case Node.addSec node inst'' pdx of
306
       Ok node' -> Node.removeSec node' inst'' ==? node
307
       _ -> failTest "Can't add instance"
308

    
309
testSuite "HTools/Node"
310
            [ 'prop_setAlias
311
            , 'prop_setOffline
312
            , 'prop_setMcpu
313
            , 'prop_setFmemGreater
314
            , 'prop_setFmemExact
315
            , 'prop_setXmem
316
            , 'prop_addPriFM
317
            , 'prop_addPriFD
318
            , 'prop_addPriFC
319
            , 'prop_addPri_NoN1Fail
320
            , 'prop_addSec
321
            , 'prop_addOfflinePri
322
            , 'prop_addOfflineSec
323
            , 'prop_rMem
324
            , 'prop_setMdsk
325
            , 'prop_tagMaps_idempotent
326
            , 'prop_tagMaps_reject
327
            , 'prop_showField
328
            , 'prop_computeGroups
329
            , 'prop_addPri_idempotent
330
            , 'prop_addSec_idempotent
331
            ]