Statistics
| Branch: | Tag: | Revision:

root / test / hs / Test / Ganeti / HTools / Node.hs @ c09359ee

History | View | Annotate | Download (15.8 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
  , genNodeList
36
  , genUniqueNodeList
37
  ) where
38

    
39
import Test.QuickCheck
40
import Test.HUnit
41

    
42
import Control.Monad
43
import qualified Data.Map as Map
44
import qualified Data.Graph as Graph
45
import Data.List
46

    
47
import Test.Ganeti.TestHelper
48
import Test.Ganeti.TestCommon
49
import Test.Ganeti.TestHTools
50
import Test.Ganeti.HTools.Instance ( genInstanceSmallerThanNode
51
                                   , genInstanceList
52
                                   , genInstanceOnNodeList)
53

    
54
import Ganeti.BasicTypes
55
import qualified Ganeti.HTools.Loader as Loader
56
import qualified Ganeti.HTools.Container as Container
57
import qualified Ganeti.HTools.Instance as Instance
58
import qualified Ganeti.HTools.Node as Node
59
import qualified Ganeti.HTools.Types as Types
60
import qualified Ganeti.HTools.Graph as HGraph
61

    
62
{-# ANN module "HLint: ignore Use camelCase" #-}
63

    
64
-- * Arbitrary instances
65

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

    
97
-- | Helper function to generate a sane node.
98
genOnlineNode :: Gen Node.Node
99
genOnlineNode =
100
  arbitrary `suchThat` (\n -> not (Node.offline n) &&
101
                              not (Node.failN1 n) &&
102
                              Node.availDisk n > 0 &&
103
                              Node.availMem n > 0 &&
104
                              Node.availCpu n > 0)
105

    
106
-- and a random node
107
instance Arbitrary Node.Node where
108
  arbitrary = genNode Nothing Nothing
109

    
110
-- | Node list generator.
111
-- Given a node generator, create a random length node list.  Note that "real"
112
-- clusters always have at least one node, so we don't generate empty node
113
-- lists here.
114
genNodeList :: Gen Node.Node -> Gen Node.List
115
genNodeList ngen = fmap (snd . Loader.assignIndices) names_nodes
116
    where names_nodes = (fmap . map) (\n -> (Node.name n, n)) $ listOf1 ngen
117

    
118
-- | Node list generator where node names are unique
119
genUniqueNodeList :: Gen Node.Node -> Gen (Node.List, Types.NameAssoc)
120
genUniqueNodeList ngen = (do
121
  nl <- genNodeList ngen
122
  let na = (fst . Loader.assignIndices) $
123
           map (\n -> (Node.name n, n)) (Container.elems nl)
124
  return (nl, na)) `suchThat`
125
    (\(nl, na) -> Container.size nl == Map.size na)
126

    
127
-- | Generate a node list, an instance list, and a node graph.
128
-- We choose instances with nodes contained in the node list.
129
genNodeGraph :: Gen (Maybe Graph.Graph, Node.List, Instance.List)
130
genNodeGraph = do
131
  nl <- genNodeList genOnlineNode `suchThat` ((2<=).Container.size)
132
  il <- genInstanceList (genInstanceOnNodeList nl)
133
  return (Node.mkNodeGraph nl il, nl, il)
134

    
135
-- * Test cases
136

    
137
prop_setAlias :: Node.Node -> String -> Bool
138
prop_setAlias node name =
139
  Node.name newnode == Node.name node &&
140
  Node.alias newnode == name
141
    where newnode = Node.setAlias node name
142

    
143
prop_setOffline :: Node.Node -> Bool -> Property
144
prop_setOffline node status =
145
  Node.offline newnode ==? status
146
    where newnode = Node.setOffline node status
147

    
148
prop_setXmem :: Node.Node -> Int -> Property
149
prop_setXmem node xm =
150
  Node.xMem newnode ==? xm
151
    where newnode = Node.setXmem node xm
152

    
153
prop_setMcpu :: Node.Node -> Double -> Property
154
prop_setMcpu node mc =
155
  Types.iPolicyVcpuRatio (Node.iPolicy newnode) ==? mc
156
    where newnode = Node.setMcpu node mc
157

    
158
prop_setFmemGreater :: Node.Node -> Int -> Property
159
prop_setFmemGreater node new_mem =
160
  not (Node.failN1 node) && (Node.rMem node >= 0) &&
161
  (new_mem > Node.rMem node) ==>
162
  not (Node.failN1 (Node.setFmem node new_mem))
163

    
164
prop_setFmemExact :: Node.Node -> Property
165
prop_setFmemExact node =
166
  not (Node.failN1 node) && (Node.rMem node >= 0) ==>
167
  not (Node.failN1 (Node.setFmem node (Node.rMem node)))
168

    
169
-- Check if adding an instance that consumes exactly all reserved
170
-- memory does not raise an N+1 error
171
prop_addPri_NoN1Fail :: Property
172
prop_addPri_NoN1Fail =
173
  forAll genOnlineNode $ \node ->
174
  forAll (genInstanceSmallerThanNode node) $ \inst ->
175
  let inst' = inst { Instance.mem = Node.fMem node - Node.rMem node }
176
  in (Node.addPri node inst' /=? Bad Types.FailN1)
177

    
178
-- | Check that an instance add with too high memory or disk will be
179
-- rejected.
180
prop_addPriFM :: Node.Node -> Instance.Instance -> Property
181
prop_addPriFM node inst =
182
  Instance.mem inst >= Node.fMem node && not (Node.failN1 node) &&
183
  not (Instance.isOffline inst) ==>
184
  (Node.addPri node inst'' ==? Bad Types.FailMem)
185
  where inst' = setInstanceSmallerThanNode node inst
186
        inst'' = inst' { Instance.mem = Instance.mem inst }
187

    
188
-- | Check that adding a primary instance with too much disk fails
189
-- with type FailDisk.
190
prop_addPriFD :: Node.Node -> Instance.Instance -> Property
191
prop_addPriFD node inst =
192
  forAll (elements Instance.localStorageTemplates) $ \dt ->
193
  Instance.dsk inst >= Node.fDsk node && not (Node.failN1 node) ==>
194
  let inst' = setInstanceSmallerThanNode node inst
195
      inst'' = inst' { Instance.dsk = Instance.dsk inst
196
                     , Instance.diskTemplate = dt }
197
  in (Node.addPri node inst'' ==? Bad Types.FailDisk)
198

    
199
-- | Check that adding a primary instance with too many VCPUs fails
200
-- with type FailCPU.
201
prop_addPriFC :: Property
202
prop_addPriFC =
203
  forAll (choose (1, maxCpu)) $ \extra ->
204
  forAll genOnlineNode $ \node ->
205
  forAll (arbitrary `suchThat` Instance.notOffline) $ \inst ->
206
  let inst' = setInstanceSmallerThanNode node inst
207
      inst'' = inst' { Instance.vcpus = Node.availCpu node + extra }
208
  in case Node.addPri node inst'' of
209
       Bad Types.FailCPU -> passTest
210
       v -> failTest $ "Expected OpFail FailCPU, but got " ++ show v
211

    
212
-- | Check that an instance add with too high memory or disk will be
213
-- rejected.
214
prop_addSec :: Node.Node -> Instance.Instance -> Int -> Property
215
prop_addSec node inst pdx =
216
  ((Instance.mem inst >= (Node.fMem node - Node.rMem node) &&
217
    not (Instance.isOffline inst)) ||
218
   Instance.dsk inst >= Node.fDsk node) &&
219
  not (Node.failN1 node) ==>
220
      isBad (Node.addSec node inst pdx)
221

    
222
-- | Check that an offline instance with reasonable disk size but
223
-- extra mem/cpu can always be added.
224
prop_addOfflinePri :: NonNegative Int -> NonNegative Int -> Property
225
prop_addOfflinePri (NonNegative extra_mem) (NonNegative extra_cpu) =
226
  forAll genOnlineNode $ \node ->
227
  forAll (genInstanceSmallerThanNode node) $ \inst ->
228
  let inst' = inst { Instance.runSt = Types.StatusOffline
229
                   , Instance.mem = Node.availMem node + extra_mem
230
                   , Instance.vcpus = Node.availCpu node + extra_cpu }
231
  in case Node.addPri node inst' of
232
       Ok _ -> passTest
233
       v -> failTest $ "Expected OpGood, but got: " ++ show v
234

    
235
-- | Check that an offline instance with reasonable disk size but
236
-- extra mem/cpu can always be added.
237
prop_addOfflineSec :: NonNegative Int -> NonNegative Int
238
                   -> Types.Ndx -> Property
239
prop_addOfflineSec (NonNegative extra_mem) (NonNegative extra_cpu) pdx =
240
  forAll genOnlineNode $ \node ->
241
  forAll (genInstanceSmallerThanNode node) $ \inst ->
242
  let inst' = inst { Instance.runSt = Types.StatusOffline
243
                   , Instance.mem = Node.availMem node + extra_mem
244
                   , Instance.vcpus = Node.availCpu node + extra_cpu
245
                   , Instance.diskTemplate = Types.DTDrbd8 }
246
  in case Node.addSec node inst' pdx of
247
       Ok _ -> passTest
248
       v -> failTest $ "Expected OpGood/OpGood, but got: " ++ show v
249

    
250
-- | Checks for memory reservation changes.
251
prop_rMem :: Instance.Instance -> Property
252
prop_rMem inst =
253
  not (Instance.isOffline inst) ==>
254
  forAll (genOnlineNode `suchThat` ((> Types.unitMem) . Node.fMem)) $ \node ->
255
  -- ab = auto_balance, nb = non-auto_balance
256
  -- we use -1 as the primary node of the instance
257
  let inst' = inst { Instance.pNode = -1, Instance.autoBalance = True
258
                   , Instance.diskTemplate = Types.DTDrbd8 }
259
      inst_ab = setInstanceSmallerThanNode node inst'
260
      inst_nb = inst_ab { Instance.autoBalance = False }
261
      -- now we have the two instances, identical except the
262
      -- autoBalance attribute
263
      orig_rmem = Node.rMem node
264
      inst_idx = Instance.idx inst_ab
265
      node_add_ab = Node.addSec node inst_ab (-1)
266
      node_add_nb = Node.addSec node inst_nb (-1)
267
      node_del_ab = liftM (`Node.removeSec` inst_ab) node_add_ab
268
      node_del_nb = liftM (`Node.removeSec` inst_nb) node_add_nb
269
  in case (node_add_ab, node_add_nb, node_del_ab, node_del_nb) of
270
       (Ok a_ab, Ok a_nb,
271
        Ok d_ab, Ok d_nb) ->
272
         printTestCase "Consistency checks failed" $
273
           Node.rMem a_ab >  orig_rmem &&
274
           Node.rMem a_ab - orig_rmem == Instance.mem inst_ab &&
275
           Node.rMem a_nb == orig_rmem &&
276
           Node.rMem d_ab == orig_rmem &&
277
           Node.rMem d_nb == orig_rmem &&
278
           -- this is not related to rMem, but as good a place to
279
           -- test as any
280
           inst_idx `elem` Node.sList a_ab &&
281
           inst_idx `notElem` Node.sList d_ab
282
       x -> failTest $ "Failed to add/remove instances: " ++ show x
283

    
284
-- | Check mdsk setting.
285
prop_setMdsk :: Node.Node -> SmallRatio -> Bool
286
prop_setMdsk node mx =
287
  Node.loDsk node' >= 0 &&
288
  fromIntegral (Node.loDsk node') <= Node.tDsk node &&
289
  Node.availDisk node' >= 0 &&
290
  Node.availDisk node' <= Node.fDsk node' &&
291
  fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
292
  Node.mDsk node' == mx'
293
    where node' = Node.setMdsk node mx'
294
          SmallRatio mx' = mx
295

    
296
-- Check tag maps
297
prop_tagMaps_idempotent :: Property
298
prop_tagMaps_idempotent =
299
  forAll genTags $ \tags ->
300
  Node.delTags (Node.addTags m tags) tags ==? m
301
    where m = Map.empty
302

    
303
prop_tagMaps_reject :: Property
304
prop_tagMaps_reject =
305
  forAll (genTags `suchThat` (not . null)) $ \tags ->
306
  let m = Node.addTags Map.empty tags
307
  in all (\t -> Node.rejectAddTags m [t]) tags
308

    
309
prop_showField :: Node.Node -> Property
310
prop_showField node =
311
  forAll (elements Node.defaultFields) $ \ field ->
312
  fst (Node.showHeader field) /= Types.unknownField &&
313
  Node.showField node field /= Types.unknownField
314

    
315
prop_computeGroups :: [Node.Node] -> Bool
316
prop_computeGroups nodes =
317
  let ng = Node.computeGroups nodes
318
      onlyuuid = map fst ng
319
  in length nodes == sum (map (length . snd) ng) &&
320
     all (\(guuid, ns) -> all ((== guuid) . Node.group) ns) ng &&
321
     length (nub onlyuuid) == length onlyuuid &&
322
     (null nodes || not (null ng))
323

    
324
-- Check idempotence of add/remove operations
325
prop_addPri_idempotent :: Property
326
prop_addPri_idempotent =
327
  forAll genOnlineNode $ \node ->
328
  forAll (genInstanceSmallerThanNode node) $ \inst ->
329
  case Node.addPri node inst of
330
    Ok node' -> Node.removePri node' inst ==? node
331
    _ -> failTest "Can't add instance"
332

    
333
prop_addSec_idempotent :: Property
334
prop_addSec_idempotent =
335
  forAll genOnlineNode $ \node ->
336
  forAll (genInstanceSmallerThanNode node) $ \inst ->
337
  let pdx = Node.idx node + 1
338
      inst' = Instance.setPri inst pdx
339
      inst'' = inst' { Instance.diskTemplate = Types.DTDrbd8 }
340
  in case Node.addSec node inst'' pdx of
341
       Ok node' -> Node.removeSec node' inst'' ==? node
342
       _ -> failTest "Can't add instance"
343

    
344
-- | Check that no graph is created on an empty node list.
345
case_emptyNodeList :: Assertion
346
case_emptyNodeList =
347
  assertEqual "" Nothing $ Node.mkNodeGraph emptynodes emptyinstances
348
    where emptynodes = Container.empty :: Node.List
349
          emptyinstances = Container.empty :: Instance.List
350

    
351
-- | Check that the number of vertices of a nodegraph is equal to the number of
352
-- nodes in the original node list.
353
prop_numVertices :: Property
354
prop_numVertices =
355
  forAll genNodeGraph $ \(graph, nl, _) ->
356
    (fmap numvertices graph ==? Just (Container.size nl))
357
    where numvertices = length . Graph.vertices
358

    
359
-- | Check that the number of edges of a nodegraph is equal to twice the number
360
-- of instances with secondary nodes in the original instance list.
361
prop_numEdges :: Property
362
prop_numEdges =
363
  forAll genNodeGraph $ \(graph, _, il) ->
364
    (fmap numedges graph ==? Just (numwithsec il * 2))
365
    where numedges = length . Graph.edges
366
          numwithsec = length . filter Instance.hasSecondary . Container.elems
367

    
368
-- | Check that a node graph is colorable.
369
prop_nodeGraphIsColorable :: Property
370
prop_nodeGraphIsColorable =
371
  forAll genNodeGraph $ \(graph, _, _) ->
372
    fmap HGraph.isColorable graph ==? Just True
373

    
374
-- | Check that each edge in a nodegraph is an instance.
375
prop_instanceIsEdge :: Property
376
prop_instanceIsEdge =
377
  forAll genNodeGraph $ \(graph, _, il) ->
378
    fmap (\g -> all (`isEdgeOn` g) (iwithsec il)) graph ==? Just True
379
    where i `isEdgeOn` g = iEdges i `intersect` Graph.edges g == iEdges i
380
          iEdges i = [ (Instance.pNode i, Instance.sNode i)
381
                     , (Instance.sNode i, Instance.pNode i)]
382
          iwithsec = filter Instance.hasSecondary . Container.elems
383

    
384
-- | Check that each instance in an edge in the resulting nodegraph.
385
prop_edgeIsInstance :: Property
386
prop_edgeIsInstance =
387
  forAll genNodeGraph $ \(graph, _, il) ->
388
    fmap (all (`isInstanceIn` il).Graph.edges) graph ==? Just True
389
      where e `isInstanceIn` il = any (`hasNodes` e) (Container.elems il)
390
            i `hasNodes` (v1,v2) =
391
              Instance.allNodes i `elem` permutations [v1,v2]
392

    
393
-- | List of tests for the Node module.
394
testSuite "HTools/Node"
395
            [ 'prop_setAlias
396
            , 'prop_setOffline
397
            , 'prop_setMcpu
398
            , 'prop_setFmemGreater
399
            , 'prop_setFmemExact
400
            , 'prop_setXmem
401
            , 'prop_addPriFM
402
            , 'prop_addPriFD
403
            , 'prop_addPriFC
404
            , 'prop_addPri_NoN1Fail
405
            , 'prop_addSec
406
            , 'prop_addOfflinePri
407
            , 'prop_addOfflineSec
408
            , 'prop_rMem
409
            , 'prop_setMdsk
410
            , 'prop_tagMaps_idempotent
411
            , 'prop_tagMaps_reject
412
            , 'prop_showField
413
            , 'prop_computeGroups
414
            , 'prop_addPri_idempotent
415
            , 'prop_addSec_idempotent
416
            , 'case_emptyNodeList
417
            , 'prop_numVertices
418
            , 'prop_numEdges
419
            , 'prop_nodeGraphIsColorable
420
            , 'prop_edgeIsInstance
421
            , 'prop_instanceIsEdge
422
            ]