Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / QC.hs @ 8bcdde0c

History | View | Annotate | Download (13.8 kB)

1
{-| Unittests for ganeti-htools
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2009 Google Inc.
8

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

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

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

    
24
-}
25

    
26
module Ganeti.HTools.QC
27
    ( testPeerMap
28
    , testContainer
29
    , testInstance
30
    , testNode
31
    , testText
32
    , testCluster
33
    ) where
34

    
35
import Test.QuickCheck
36
import Test.QuickCheck.Batch
37
import Data.Maybe
38
import qualified Data.Map
39
import qualified Ganeti.HTools.CLI as CLI
40
import qualified Ganeti.HTools.Cluster as Cluster
41
import qualified Ganeti.HTools.Container as Container
42
import qualified Ganeti.HTools.IAlloc as IAlloc
43
import qualified Ganeti.HTools.Instance as Instance
44
import qualified Ganeti.HTools.Loader as Loader
45
import qualified Ganeti.HTools.Node as Node
46
import qualified Ganeti.HTools.PeerMap as PeerMap
47
import qualified Ganeti.HTools.Text as Text
48
import qualified Ganeti.HTools.Types as Types
49
import qualified Ganeti.HTools.Utils as Utils
50

    
51
-- | Maximum memory (1TiB, somewhat random value)
52
maxMem :: Int
53
maxMem = 1024 * 1024
54

    
55
-- | Maximum disk (8TiB, somewhat random value)
56
maxDsk :: Int
57
maxDsk = 1024 * 1024 * 8
58

    
59
-- | Max CPUs (1024, somewhat random value)
60
maxCpu :: Int
61
maxCpu = 1024
62

    
63
-- | Simple checker for whether OpResult is fail or pass
64
isFailure :: Types.OpResult a -> Bool
65
isFailure (Types.OpFail _) = True
66
isFailure _ = False
67

    
68
-- | Simple checker for whether Result is fail or pass
69
isOk :: Types.Result a -> Bool
70
isOk (Types.Ok _ ) = True
71
isOk _ = False
72

    
73
-- copied from the introduction to quickcheck
74
instance Arbitrary Char where
75
    arbitrary = choose ('\32', '\128')
76

    
77
-- let's generate a random instance
78
instance Arbitrary Instance.Instance where
79
    arbitrary = do
80
      name <- arbitrary
81
      mem <- choose (0, maxMem)
82
      dsk <- choose (0, maxDsk)
83
      run_st <- elements ["ERROR_up", "ERROR_down", "ADMIN_down"
84
                         , "ERROR_nodedown", "ERROR_nodeoffline"
85
                         , "running"
86
                         , "no_such_status1", "no_such_status2"]
87
      pn <- arbitrary
88
      sn <- arbitrary
89
      vcpus <- choose (0, maxCpu)
90
      return $ Instance.create name mem dsk vcpus run_st [] pn sn
91

    
92
-- and a random node
93
instance Arbitrary Node.Node where
94
    arbitrary = do
95
      name <- arbitrary
96
      mem_t <- choose (0, maxMem)
97
      mem_f <- choose (0, mem_t)
98
      mem_n <- choose (0, mem_t - mem_f)
99
      dsk_t <- choose (0, maxDsk)
100
      dsk_f <- choose (0, dsk_t)
101
      cpu_t <- choose (0, maxCpu)
102
      offl <- arbitrary
103
      let n = Node.create name (fromIntegral mem_t) mem_n mem_f
104
              (fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) offl
105
          n' = Node.buildPeers n Container.empty
106
      return n'
107

    
108
setInstanceSmallerThanNode node inst =
109
    inst { Instance.mem = (Node.availMem node) `div` 2
110
         , Instance.dsk = (Node.availDisk node) `div` 2
111
         , Instance.vcpus = (Node.availCpu node) `div` 2
112
         }
113

    
114
-- | Make sure add is idempotent
115
prop_PeerMap_addIdempotent pmap key em =
116
    fn puniq == fn (fn puniq)
117
    where _types = (pmap::PeerMap.PeerMap,
118
                    key::PeerMap.Key, em::PeerMap.Elem)
119
          fn = PeerMap.add key em
120
          puniq = PeerMap.accumArray const pmap
121

    
122
-- | Make sure remove is idempotent
123
prop_PeerMap_removeIdempotent pmap key =
124
    fn puniq == fn (fn puniq)
125
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
126
          fn = PeerMap.remove key
127
          puniq = PeerMap.accumArray const pmap
128

    
129
-- | Make sure a missing item returns 0
130
prop_PeerMap_findMissing pmap key =
131
    PeerMap.find key (PeerMap.remove key puniq) == 0
132
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
133
          puniq = PeerMap.accumArray const pmap
134

    
135
-- | Make sure an added item is found
136
prop_PeerMap_addFind pmap key em =
137
    PeerMap.find key (PeerMap.add key em puniq) == em
138
    where _types = (pmap::PeerMap.PeerMap,
139
                    key::PeerMap.Key, em::PeerMap.Elem)
140
          puniq = PeerMap.accumArray const pmap
141

    
142
-- | Manual check that maxElem returns the maximum indeed, or 0 for null
143
prop_PeerMap_maxElem pmap =
144
    PeerMap.maxElem puniq == if null puniq then 0
145
                             else (maximum . snd . unzip) puniq
146
    where _types = pmap::PeerMap.PeerMap
147
          puniq = PeerMap.accumArray const pmap
148

    
149
testPeerMap =
150
    [ run prop_PeerMap_addIdempotent
151
    , run prop_PeerMap_removeIdempotent
152
    , run prop_PeerMap_maxElem
153
    , run prop_PeerMap_addFind
154
    , run prop_PeerMap_findMissing
155
    ]
156

    
157
-- Container tests
158

    
159
prop_Container_addTwo cdata i1 i2 =
160
    fn i1 i2 cont == fn i2 i1 cont &&
161
       fn i1 i2 cont == fn i1 i2 (fn i1 i2 cont)
162
    where _types = (cdata::[Int],
163
                    i1::Int, i2::Int)
164
          cont = foldl (\c x -> Container.add x x c) Container.empty cdata
165
          fn x1 x2 = Container.addTwo x1 x1 x2 x2
166

    
167
testContainer =
168
    [ run prop_Container_addTwo ]
169

    
170
-- Simple instance tests, we only have setter/getters
171

    
172
prop_Instance_setIdx inst idx =
173
    Instance.idx (Instance.setIdx inst idx) == idx
174
    where _types = (inst::Instance.Instance, idx::Types.Idx)
175

    
176
prop_Instance_setName inst name =
177
    Instance.name (Instance.setName inst name) == name
178
    where _types = (inst::Instance.Instance, name::String)
179

    
180
prop_Instance_setPri inst pdx =
181
    Instance.pNode (Instance.setPri inst pdx) == pdx
182
    where _types = (inst::Instance.Instance, pdx::Types.Ndx)
183

    
184
prop_Instance_setSec inst sdx =
185
    Instance.sNode (Instance.setSec inst sdx) == sdx
186
    where _types = (inst::Instance.Instance, sdx::Types.Ndx)
187

    
188
prop_Instance_setBoth inst pdx sdx =
189
    Instance.pNode si == pdx && Instance.sNode si == sdx
190
    where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
191
          si = Instance.setBoth inst pdx sdx
192

    
193
prop_Instance_runStatus_True inst =
194
    let run_st = Instance.running inst
195
        run_tx = Instance.runSt inst
196
    in
197
      run_tx `elem` Instance.runningStates ==> run_st
198

    
199
prop_Instance_runStatus_False inst =
200
    let run_st = Instance.running inst
201
        run_tx = Instance.runSt inst
202
    in
203
      run_tx `notElem` Instance.runningStates ==> not run_st
204

    
205
prop_Instance_shrinkMG inst =
206
    Instance.mem inst >= 2 * Types.unitMem ==>
207
        case Instance.shrinkByType inst Types.FailMem of
208
          Types.Ok inst' ->
209
              Instance.mem inst' == Instance.mem inst - Types.unitMem
210
          _ -> False
211
    where _types = (inst::Instance.Instance)
212

    
213
prop_Instance_shrinkMF inst =
214
    Instance.mem inst < 2 * Types.unitMem ==>
215
        not . isOk $ Instance.shrinkByType inst Types.FailMem
216
    where _types = (inst::Instance.Instance)
217

    
218
prop_Instance_shrinkCG inst =
219
    Instance.vcpus inst >= 2 * Types.unitCpu ==>
220
        case Instance.shrinkByType inst Types.FailCPU of
221
          Types.Ok inst' ->
222
              Instance.vcpus inst' == Instance.vcpus inst - Types.unitCpu
223
          _ -> False
224
    where _types = (inst::Instance.Instance)
225

    
226
prop_Instance_shrinkCF inst =
227
    Instance.vcpus inst < 2 * Types.unitCpu ==>
228
        not . isOk $ Instance.shrinkByType inst Types.FailCPU
229
    where _types = (inst::Instance.Instance)
230

    
231
prop_Instance_shrinkDG inst =
232
    Instance.dsk inst >= 2 * Types.unitDsk ==>
233
        case Instance.shrinkByType inst Types.FailDisk of
234
          Types.Ok inst' ->
235
              Instance.dsk inst' == Instance.dsk inst - Types.unitDsk
236
          _ -> False
237
    where _types = (inst::Instance.Instance)
238

    
239
prop_Instance_shrinkDF inst =
240
    Instance.dsk inst < 2 * Types.unitDsk ==>
241
        not . isOk $ Instance.shrinkByType inst Types.FailDisk
242
    where _types = (inst::Instance.Instance)
243

    
244
prop_Instance_setMovable inst m =
245
    Instance.movable inst' == m
246
    where _types = (inst::Instance.Instance, m::Bool)
247
          inst' = Instance.setMovable inst m
248

    
249
testInstance =
250
    [ run prop_Instance_setIdx
251
    , run prop_Instance_setName
252
    , run prop_Instance_setPri
253
    , run prop_Instance_setSec
254
    , run prop_Instance_setBoth
255
    , run prop_Instance_runStatus_True
256
    , run prop_Instance_runStatus_False
257
    , run prop_Instance_shrinkMG
258
    , run prop_Instance_shrinkMF
259
    , run prop_Instance_shrinkCG
260
    , run prop_Instance_shrinkCF
261
    , run prop_Instance_shrinkDG
262
    , run prop_Instance_shrinkDF
263
    , run prop_Instance_setMovable
264
    ]
265

    
266
-- Instance text loader tests
267

    
268
prop_Text_Load_Instance name mem dsk vcpus status pnode snode pdx sdx =
269
    let vcpus_s = show vcpus
270
        dsk_s = show dsk
271
        mem_s = show mem
272
        rsnode = snode ++ "a" -- non-empty secondary node
273
        rsdx = if pdx == sdx
274
               then sdx + 1
275
               else sdx
276
        ndx = [(pnode, pdx), (rsnode, rsdx)]
277
        tags = ""
278
        inst = Text.loadInst ndx
279
               [name, mem_s, dsk_s, vcpus_s, status, pnode, rsnode, tags]::
280
               Maybe (String, Instance.Instance)
281
        _types = ( name::String, mem::Int, dsk::Int
282
                 , vcpus::Int, status::String
283
                 , pnode::String, snode::String
284
                 , pdx::Types.Ndx, sdx::Types.Ndx)
285
    in
286
      case inst of
287
        Nothing -> False
288
        Just (_, i) ->
289
            (Instance.name i == name &&
290
             Instance.vcpus i == vcpus &&
291
             Instance.mem i == mem &&
292
             Instance.pNode i == pdx &&
293
             Instance.sNode i == rsdx)
294

    
295
testText =
296
    [ run prop_Text_Load_Instance
297
    ]
298

    
299
-- Node tests
300

    
301
-- | Check that an instance add with too high memory or disk will be rejected
302
prop_Node_addPriFM node inst = Instance.mem inst >= Node.fMem node &&
303
                               not (Node.failN1 node)
304
                               ==>
305
                               case Node.addPri node inst'' of
306
                                 Types.OpFail Types.FailMem -> True
307
                                 _ -> False
308
    where _types = (node::Node.Node, inst::Instance.Instance)
309
          inst' = setInstanceSmallerThanNode node inst
310
          inst'' = inst' { Instance.mem = Instance.mem inst }
311

    
312
prop_Node_addPriFD node inst = Instance.dsk inst >= Node.fDsk node &&
313
                               not (Node.failN1 node)
314
                               ==>
315
                               case Node.addPri node inst'' of
316
                                 Types.OpFail Types.FailDisk -> True
317
                                 _ -> False
318
    where _types = (node::Node.Node, inst::Instance.Instance)
319
          inst' = setInstanceSmallerThanNode node inst
320
          inst'' = inst' { Instance.dsk = Instance.dsk inst }
321

    
322
prop_Node_addPriFC node inst = Instance.vcpus inst > Node.availCpu node &&
323
                               not (Node.failN1 node)
324
                               ==>
325
                               case Node.addPri node inst'' of
326
                                 Types.OpFail Types.FailCPU -> True
327
                                 _ -> False
328
    where _types = (node::Node.Node, inst::Instance.Instance)
329
          inst' = setInstanceSmallerThanNode node inst
330
          inst'' = inst' { Instance.vcpus = Instance.vcpus inst }
331

    
332
-- | Check that an instance add with too high memory or disk will be rejected
333
prop_Node_addSec node inst pdx =
334
    (Instance.mem inst >= (Node.fMem node - Node.rMem node) ||
335
     Instance.dsk inst >= Node.fDsk node) &&
336
    not (Node.failN1 node)
337
    ==> isFailure (Node.addSec node inst pdx)
338
        where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
339

    
340
newtype SmallRatio = SmallRatio Double deriving Show
341
instance Arbitrary SmallRatio where
342
    arbitrary = do
343
      v <- choose (0, 1)
344
      return $ SmallRatio v
345

    
346
-- | Check mdsk setting
347
prop_Node_setMdsk node mx =
348
    Node.loDsk node' >= 0 &&
349
    fromIntegral (Node.loDsk node') <= Node.tDsk node &&
350
    Node.availDisk node' >= 0 &&
351
    Node.availDisk node' <= Node.fDsk node' &&
352
    fromIntegral (Node.availDisk node') <= Node.tDsk node'
353
    where _types = (node::Node.Node, mx::SmallRatio)
354
          node' = Node.setMdsk node mx'
355
          SmallRatio mx' = mx
356

    
357
-- Check tag maps
358
prop_Node_tagMaps_idempotent tags =
359
    Node.delTags (Node.addTags m tags) tags == m
360
    where _types = (tags::[String])
361
          m = Data.Map.empty
362

    
363
prop_Node_tagMaps_reject tags =
364
    not (null tags) ==>
365
    any (\t -> Node.rejectAddTags m [t]) tags
366
    where _types = (tags::[String])
367
          m = Node.addTags (Data.Map.empty) tags
368

    
369
testNode =
370
    [ run prop_Node_addPriFM
371
    , run prop_Node_addPriFD
372
    , run prop_Node_addPriFC
373
    , run prop_Node_addSec
374
    , run prop_Node_setMdsk
375
    , run prop_Node_tagMaps_idempotent
376
    , run prop_Node_tagMaps_reject
377
    ]
378

    
379

    
380
-- Cluster tests
381

    
382
-- | Check that the cluster score is close to zero for a homogeneous cluster
383
prop_Score_Zero node count =
384
    (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
385
     (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
386
    let fn = Node.buildPeers node Container.empty
387
        nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
388
        nl = Container.fromAssocList nlst
389
        score = Cluster.compCV nl
390
    -- we can't say == 0 here as the floating point errors accumulate;
391
    -- this should be much lower than the default score in CLI.hs
392
    in score <= 1e-15
393

    
394
-- | Check that cluster stats are sane
395
prop_CStats_sane node count =
396
    (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
397
     (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
398
    let fn = Node.buildPeers node Container.empty
399
        nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
400
        nl = Container.fromAssocList nlst
401
        cstats = Cluster.totalResources nl
402
    in Cluster.csAdsk cstats >= 0 &&
403
       Cluster.csAdsk cstats <= Cluster.csFdsk cstats
404

    
405
testCluster =
406
    [ run prop_Score_Zero
407
    , run prop_CStats_sane
408
    ]