Statistics
| Branch: | Tag: | Revision:

root / htest / Test / Ganeti / HTools / Node.hs @ 2e0bb81d

History | View | Annotate | Download (11.3 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 qualified Ganeti.HTools.Container as Container
49
import qualified Ganeti.HTools.Instance as Instance
50
import qualified Ganeti.HTools.Node as Node
51
import qualified Ganeti.HTools.Types as Types
52

    
53
-- * Arbitrary instances
54

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

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

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

    
99
-- * Test cases
100

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

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

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

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

    
122
-- | Check that an instance add with too high memory or disk will be
123
-- rejected.
124
prop_addPriFM :: Node.Node -> Instance.Instance -> Property
125
prop_addPriFM node inst =
126
  Instance.mem inst >= Node.fMem node && not (Node.failN1 node) &&
127
  not (Instance.isOffline inst) ==>
128
  case Node.addPri node inst'' of
129
    Types.OpFail Types.FailMem -> True
130
    _ -> False
131
  where inst' = setInstanceSmallerThanNode node inst
132
        inst'' = inst' { Instance.mem = Instance.mem inst }
133

    
134
-- | Check that adding a primary instance with too much disk fails
135
-- with type FailDisk.
136
prop_addPriFD :: Node.Node -> Instance.Instance -> Property
137
prop_addPriFD node inst =
138
  forAll (elements Instance.localStorageTemplates) $ \dt ->
139
  Instance.dsk inst >= Node.fDsk node && not (Node.failN1 node) ==>
140
  let inst' = setInstanceSmallerThanNode node inst
141
      inst'' = inst' { Instance.dsk = Instance.dsk inst
142
                     , Instance.diskTemplate = dt }
143
  in case Node.addPri node inst'' of
144
       Types.OpFail Types.FailDisk -> True
145
       _ -> False
146

    
147
-- | Check that adding a primary instance with too many VCPUs fails
148
-- with type FailCPU.
149
prop_addPriFC :: Property
150
prop_addPriFC =
151
  forAll (choose (1, maxCpu)) $ \extra ->
152
  forAll genOnlineNode $ \node ->
153
  forAll (arbitrary `suchThat` Instance.notOffline) $ \inst ->
154
  let inst' = setInstanceSmallerThanNode node inst
155
      inst'' = inst' { Instance.vcpus = Node.availCpu node + extra }
156
  in case Node.addPri node inst'' of
157
       Types.OpFail Types.FailCPU -> passTest
158
       v -> failTest $ "Expected OpFail FailCPU, but got " ++ show v
159

    
160
-- | Check that an instance add with too high memory or disk will be
161
-- rejected.
162
prop_addSec :: Node.Node -> Instance.Instance -> Int -> Property
163
prop_addSec node inst pdx =
164
  ((Instance.mem inst >= (Node.fMem node - Node.rMem node) &&
165
    not (Instance.isOffline inst)) ||
166
   Instance.dsk inst >= Node.fDsk node) &&
167
  not (Node.failN1 node) ==>
168
      isFailure (Node.addSec node inst pdx)
169

    
170
-- | Check that an offline instance with reasonable disk size but
171
-- extra mem/cpu can always be added.
172
prop_addOfflinePri :: NonNegative Int -> NonNegative Int -> Property
173
prop_addOfflinePri (NonNegative extra_mem) (NonNegative extra_cpu) =
174
  forAll genOnlineNode $ \node ->
175
  forAll (genInstanceSmallerThanNode node) $ \inst ->
176
  let inst' = inst { Instance.runSt = Types.AdminOffline
177
                   , Instance.mem = Node.availMem node + extra_mem
178
                   , Instance.vcpus = Node.availCpu node + extra_cpu }
179
  in case Node.addPri node inst' of
180
       Types.OpGood _ -> passTest
181
       v -> failTest $ "Expected OpGood, but got: " ++ show v
182

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

    
198
-- | Checks for memory reservation changes.
199
prop_rMem :: Instance.Instance -> Property
200
prop_rMem inst =
201
  not (Instance.isOffline inst) ==>
202
  forAll (genOnlineNode `suchThat` ((> Types.unitMem) . Node.fMem)) $ \node ->
203
  -- ab = auto_balance, nb = non-auto_balance
204
  -- we use -1 as the primary node of the instance
205
  let inst' = inst { Instance.pNode = -1, Instance.autoBalance = True
206
                   , Instance.diskTemplate = Types.DTDrbd8 }
207
      inst_ab = setInstanceSmallerThanNode node inst'
208
      inst_nb = inst_ab { Instance.autoBalance = False }
209
      -- now we have the two instances, identical except the
210
      -- autoBalance attribute
211
      orig_rmem = Node.rMem node
212
      inst_idx = Instance.idx inst_ab
213
      node_add_ab = Node.addSec node inst_ab (-1)
214
      node_add_nb = Node.addSec node inst_nb (-1)
215
      node_del_ab = liftM (`Node.removeSec` inst_ab) node_add_ab
216
      node_del_nb = liftM (`Node.removeSec` inst_nb) node_add_nb
217
  in case (node_add_ab, node_add_nb, node_del_ab, node_del_nb) of
218
       (Types.OpGood a_ab, Types.OpGood a_nb,
219
        Types.OpGood d_ab, Types.OpGood d_nb) ->
220
         printTestCase "Consistency checks failed" $
221
           Node.rMem a_ab >  orig_rmem &&
222
           Node.rMem a_ab - orig_rmem == Instance.mem inst_ab &&
223
           Node.rMem a_nb == orig_rmem &&
224
           Node.rMem d_ab == orig_rmem &&
225
           Node.rMem d_nb == orig_rmem &&
226
           -- this is not related to rMem, but as good a place to
227
           -- test as any
228
           inst_idx `elem` Node.sList a_ab &&
229
           inst_idx `notElem` Node.sList d_ab
230
       x -> failTest $ "Failed to add/remove instances: " ++ show x
231

    
232
-- | Check mdsk setting.
233
prop_setMdsk :: Node.Node -> SmallRatio -> Bool
234
prop_setMdsk node mx =
235
  Node.loDsk node' >= 0 &&
236
  fromIntegral (Node.loDsk node') <= Node.tDsk node &&
237
  Node.availDisk node' >= 0 &&
238
  Node.availDisk node' <= Node.fDsk node' &&
239
  fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
240
  Node.mDsk node' == mx'
241
    where node' = Node.setMdsk node mx'
242
          SmallRatio mx' = mx
243

    
244
-- Check tag maps
245
prop_tagMaps_idempotent :: Property
246
prop_tagMaps_idempotent =
247
  forAll genTags $ \tags ->
248
  Node.delTags (Node.addTags m tags) tags ==? m
249
    where m = Map.empty
250

    
251
prop_tagMaps_reject :: Property
252
prop_tagMaps_reject =
253
  forAll (genTags `suchThat` (not . null)) $ \tags ->
254
  let m = Node.addTags Map.empty tags
255
  in all (\t -> Node.rejectAddTags m [t]) tags
256

    
257
prop_showField :: Node.Node -> Property
258
prop_showField node =
259
  forAll (elements Node.defaultFields) $ \ field ->
260
  fst (Node.showHeader field) /= Types.unknownField &&
261
  Node.showField node field /= Types.unknownField
262

    
263
prop_computeGroups :: [Node.Node] -> Bool
264
prop_computeGroups nodes =
265
  let ng = Node.computeGroups nodes
266
      onlyuuid = map fst ng
267
  in length nodes == sum (map (length . snd) ng) &&
268
     all (\(guuid, ns) -> all ((== guuid) . Node.group) ns) ng &&
269
     length (nub onlyuuid) == length onlyuuid &&
270
     (null nodes || not (null ng))
271

    
272
-- Check idempotence of add/remove operations
273
prop_addPri_idempotent :: Property
274
prop_addPri_idempotent =
275
  forAll genOnlineNode $ \node ->
276
  forAll (genInstanceSmallerThanNode node) $ \inst ->
277
  case Node.addPri node inst of
278
    Types.OpGood node' -> Node.removePri node' inst ==? node
279
    _ -> failTest "Can't add instance"
280

    
281
prop_addSec_idempotent :: Property
282
prop_addSec_idempotent =
283
  forAll genOnlineNode $ \node ->
284
  forAll (genInstanceSmallerThanNode node) $ \inst ->
285
  let pdx = Node.idx node + 1
286
      inst' = Instance.setPri inst pdx
287
      inst'' = inst' { Instance.diskTemplate = Types.DTDrbd8 }
288
  in case Node.addSec node inst'' pdx of
289
       Types.OpGood node' -> Node.removeSec node' inst'' ==? node
290
       _ -> failTest "Can't add instance"
291

    
292
testSuite "HTools/Node"
293
            [ 'prop_setAlias
294
            , 'prop_setOffline
295
            , 'prop_setMcpu
296
            , 'prop_setXmem
297
            , 'prop_addPriFM
298
            , 'prop_addPriFD
299
            , 'prop_addPriFC
300
            , 'prop_addSec
301
            , 'prop_addOfflinePri
302
            , 'prop_addOfflineSec
303
            , 'prop_rMem
304
            , 'prop_setMdsk
305
            , 'prop_tagMaps_idempotent
306
            , 'prop_tagMaps_reject
307
            , 'prop_showField
308
            , 'prop_computeGroups
309
            , 'prop_addPri_idempotent
310
            , 'prop_addSec_idempotent
311
            ]