Statistics
| Branch: | Tag: | Revision:

root / htest / Test / Ganeti / HTools / Node.hs @ dd77da99

History | View | Annotate | Download (11.2 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
-- | Check that an instance add with too high memory or disk will be
124
-- rejected.
125
prop_addPriFM :: Node.Node -> Instance.Instance -> Property
126
prop_addPriFM node inst =
127
  Instance.mem inst >= Node.fMem node && not (Node.failN1 node) &&
128
  not (Instance.isOffline inst) ==>
129
  (Node.addPri node inst'' ==? Bad Types.FailMem)
130
  where inst' = setInstanceSmallerThanNode node inst
131
        inst'' = inst' { Instance.mem = Instance.mem inst }
132

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

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

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

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

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

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

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

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

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

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

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

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

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

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