Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / QC.hs @ 5f828ce4

History | View | Annotate | Download (40 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2

    
3
{-| Unittests for ganeti-htools.
4

    
5
-}
6

    
7
{-
8

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

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

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

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

    
26
-}
27

    
28
module Ganeti.HTools.QC
29
    ( testUtils
30
    , testPeerMap
31
    , testContainer
32
    , testInstance
33
    , testNode
34
    , testText
35
    , testOpCodes
36
    , testJobs
37
    , testCluster
38
    , testLoader
39
    , testTypes
40
    ) where
41

    
42
import Test.QuickCheck
43
import Data.List (findIndex, intercalate, nub, isPrefixOf)
44
import Data.Maybe
45
import Control.Monad
46
import qualified Text.JSON as J
47
import qualified Data.Map
48
import qualified Data.IntMap as IntMap
49
import qualified Ganeti.OpCodes as OpCodes
50
import qualified Ganeti.Jobs as Jobs
51
import qualified Ganeti.Luxi
52
import qualified Ganeti.HTools.CLI as CLI
53
import qualified Ganeti.HTools.Cluster as Cluster
54
import qualified Ganeti.HTools.Container as Container
55
import qualified Ganeti.HTools.ExtLoader
56
import qualified Ganeti.HTools.IAlloc as IAlloc
57
import qualified Ganeti.HTools.Instance as Instance
58
import qualified Ganeti.HTools.Loader as Loader
59
import qualified Ganeti.HTools.Luxi
60
import qualified Ganeti.HTools.Node as Node
61
import qualified Ganeti.HTools.Group as Group
62
import qualified Ganeti.HTools.PeerMap as PeerMap
63
import qualified Ganeti.HTools.Rapi
64
import qualified Ganeti.HTools.Simu
65
import qualified Ganeti.HTools.Text as Text
66
import qualified Ganeti.HTools.Types as Types
67
import qualified Ganeti.HTools.Utils as Utils
68
import qualified Ganeti.HTools.Version
69
import qualified Ganeti.Constants as C
70

    
71
import qualified Ganeti.HTools.Program.Hail
72
import qualified Ganeti.HTools.Program.Hbal
73
import qualified Ganeti.HTools.Program.Hscan
74
import qualified Ganeti.HTools.Program.Hspace
75

    
76
import Ganeti.HTools.QCHelper (testSuite)
77

    
78
-- * Constants
79

    
80
-- | Maximum memory (1TiB, somewhat random value).
81
maxMem :: Int
82
maxMem = 1024 * 1024
83

    
84
-- | Maximum disk (8TiB, somewhat random value).
85
maxDsk :: Int
86
maxDsk = 1024 * 1024 * 8
87

    
88
-- | Max CPUs (1024, somewhat random value).
89
maxCpu :: Int
90
maxCpu = 1024
91

    
92
defGroup :: Group.Group
93
defGroup = flip Group.setIdx 0 $
94
               Group.create "default" Utils.defaultGroupID
95
                    Types.AllocPreferred
96

    
97
defGroupList :: Group.List
98
defGroupList = Container.fromList [(Group.idx defGroup, defGroup)]
99

    
100
defGroupAssoc :: Data.Map.Map String Types.Gdx
101
defGroupAssoc = Data.Map.singleton (Group.uuid defGroup) (Group.idx defGroup)
102

    
103
-- * Helper functions
104

    
105
-- | Simple checker for whether OpResult is fail or pass.
106
isFailure :: Types.OpResult a -> Bool
107
isFailure (Types.OpFail _) = True
108
isFailure _ = False
109

    
110
-- | Checks for equality with proper annotation.
111
(==?) :: (Show a, Eq a) => a -> a -> Property
112
(==?) x y = printTestCase
113
            ("Expected equality, but '" ++
114
             show x ++ "' /= '" ++ show y ++ "'") (x == y)
115
infix 3 ==?
116

    
117
-- | Update an instance to be smaller than a node.
118
setInstanceSmallerThanNode node inst =
119
    inst { Instance.mem = Node.availMem node `div` 2
120
         , Instance.dsk = Node.availDisk node `div` 2
121
         , Instance.vcpus = Node.availCpu node `div` 2
122
         }
123

    
124
-- | Create an instance given its spec.
125
createInstance mem dsk vcpus =
126
    Instance.create "inst-unnamed" mem dsk vcpus "running" [] True (-1) (-1)
127
                    Types.DTDrbd8
128

    
129
-- | Create a small cluster by repeating a node spec.
130
makeSmallCluster :: Node.Node -> Int -> Node.List
131
makeSmallCluster node count =
132
    let fn = Node.buildPeers node Container.empty
133
        namelst = map (\n -> (Node.name n, n)) (replicate count fn)
134
        (_, nlst) = Loader.assignIndices namelst
135
    in nlst
136

    
137
-- | Checks if a node is "big" enough.
138
isNodeBig :: Node.Node -> Int -> Bool
139
isNodeBig node size = Node.availDisk node > size * Types.unitDsk
140
                      && Node.availMem node > size * Types.unitMem
141
                      && Node.availCpu node > size * Types.unitCpu
142

    
143
canBalance :: Cluster.Table -> Bool -> Bool -> Bool -> Bool
144
canBalance tbl dm im evac = isJust $ Cluster.tryBalance tbl dm im evac 0 0
145

    
146
-- | Assigns a new fresh instance to a cluster; this is not
147
-- allocation, so no resource checks are done.
148
assignInstance :: Node.List -> Instance.List -> Instance.Instance ->
149
                  Types.Idx -> Types.Idx ->
150
                  (Node.List, Instance.List)
151
assignInstance nl il inst pdx sdx =
152
  let pnode = Container.find pdx nl
153
      snode = Container.find sdx nl
154
      maxiidx = if Container.null il
155
                then 0
156
                else fst (Container.findMax il) + 1
157
      inst' = inst { Instance.idx = maxiidx,
158
                     Instance.pNode = pdx, Instance.sNode = sdx }
159
      pnode' = Node.setPri pnode inst'
160
      snode' = Node.setSec snode inst'
161
      nl' = Container.addTwo pdx pnode' sdx snode' nl
162
      il' = Container.add maxiidx inst' il
163
  in (nl', il')
164

    
165
-- * Arbitrary instances
166

    
167
-- | Defines a DNS name.
168
newtype DNSChar = DNSChar { dnsGetChar::Char }
169

    
170
instance Arbitrary DNSChar where
171
    arbitrary = do
172
      x <- elements (['a'..'z'] ++ ['0'..'9'] ++ "_-")
173
      return (DNSChar x)
174

    
175
getName :: Gen String
176
getName = do
177
  n <- choose (1, 64)
178
  dn <- vector n::Gen [DNSChar]
179
  return (map dnsGetChar dn)
180

    
181

    
182
getFQDN :: Gen String
183
getFQDN = do
184
  felem <- getName
185
  ncomps <- choose (1, 4)
186
  frest <- vector ncomps::Gen [[DNSChar]]
187
  let frest' = map (map dnsGetChar) frest
188
  return (felem ++ "." ++ intercalate "." frest')
189

    
190
-- let's generate a random instance
191
instance Arbitrary Instance.Instance where
192
    arbitrary = do
193
      name <- getFQDN
194
      mem <- choose (0, maxMem)
195
      dsk <- choose (0, maxDsk)
196
      run_st <- elements [ C.inststErrorup
197
                         , C.inststErrordown
198
                         , C.inststAdmindown
199
                         , C.inststNodedown
200
                         , C.inststNodeoffline
201
                         , C.inststRunning
202
                         , "no_such_status1"
203
                         , "no_such_status2"]
204
      pn <- arbitrary
205
      sn <- arbitrary
206
      vcpus <- choose (0, maxCpu)
207
      return $ Instance.create name mem dsk vcpus run_st [] True pn sn
208
                               Types.DTDrbd8
209

    
210
-- | Generas an arbitrary node based on sizing information.
211
genNode :: Maybe Int -- ^ Minimum node size in terms of units
212
        -> Maybe Int -- ^ Maximum node size (when Nothing, bounded
213
                     -- just by the max... constants)
214
        -> Gen Node.Node
215
genNode min_multiplier max_multiplier = do
216
  let (base_mem, base_dsk, base_cpu) =
217
          case min_multiplier of
218
            Just mm -> (mm * Types.unitMem,
219
                        mm * Types.unitDsk,
220
                        mm * Types.unitCpu)
221
            Nothing -> (0, 0, 0)
222
      (top_mem, top_dsk, top_cpu)  =
223
          case max_multiplier of
224
            Just mm -> (mm * Types.unitMem,
225
                        mm * Types.unitDsk,
226
                        mm * Types.unitCpu)
227
            Nothing -> (maxMem, maxDsk, maxCpu)
228
  name  <- getFQDN
229
  mem_t <- choose (base_mem, top_mem)
230
  mem_f <- choose (base_mem, mem_t)
231
  mem_n <- choose (0, mem_t - mem_f)
232
  dsk_t <- choose (base_dsk, top_dsk)
233
  dsk_f <- choose (base_dsk, dsk_t)
234
  cpu_t <- choose (base_cpu, top_cpu)
235
  offl  <- arbitrary
236
  let n = Node.create name (fromIntegral mem_t) mem_n mem_f
237
          (fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) offl 0
238
  return $ Node.buildPeers n Container.empty
239

    
240
-- and a random node
241
instance Arbitrary Node.Node where
242
    arbitrary = genNode Nothing Nothing
243

    
244
-- replace disks
245
instance Arbitrary OpCodes.ReplaceDisksMode where
246
  arbitrary = elements [ OpCodes.ReplaceOnPrimary
247
                       , OpCodes.ReplaceOnSecondary
248
                       , OpCodes.ReplaceNewSecondary
249
                       , OpCodes.ReplaceAuto
250
                       ]
251

    
252
instance Arbitrary OpCodes.OpCode where
253
  arbitrary = do
254
    op_id <- elements [ "OP_TEST_DELAY"
255
                      , "OP_INSTANCE_REPLACE_DISKS"
256
                      , "OP_INSTANCE_FAILOVER"
257
                      , "OP_INSTANCE_MIGRATE"
258
                      ]
259
    (case op_id of
260
        "OP_TEST_DELAY" ->
261
          liftM3 OpCodes.OpTestDelay arbitrary arbitrary arbitrary
262
        "OP_INSTANCE_REPLACE_DISKS" ->
263
          liftM5 OpCodes.OpInstanceReplaceDisks arbitrary arbitrary
264
          arbitrary arbitrary arbitrary
265
        "OP_INSTANCE_FAILOVER" ->
266
          liftM3 OpCodes.OpInstanceFailover arbitrary arbitrary
267
                 arbitrary
268
        "OP_INSTANCE_MIGRATE" ->
269
          liftM5 OpCodes.OpInstanceMigrate arbitrary arbitrary
270
                 arbitrary arbitrary
271
          arbitrary
272
        _ -> fail "Wrong opcode")
273

    
274
instance Arbitrary Jobs.OpStatus where
275
  arbitrary = elements [minBound..maxBound]
276

    
277
instance Arbitrary Jobs.JobStatus where
278
  arbitrary = elements [minBound..maxBound]
279

    
280
newtype SmallRatio = SmallRatio Double deriving Show
281
instance Arbitrary SmallRatio where
282
    arbitrary = do
283
      v <- choose (0, 1)
284
      return $ SmallRatio v
285

    
286
instance Arbitrary Types.AllocPolicy where
287
  arbitrary = elements [minBound..maxBound]
288

    
289
instance Arbitrary Types.DiskTemplate where
290
  arbitrary = elements [minBound..maxBound]
291

    
292
instance Arbitrary Types.FailMode where
293
    arbitrary = elements [minBound..maxBound]
294

    
295
instance Arbitrary a => Arbitrary (Types.OpResult a) where
296
    arbitrary = arbitrary >>= \c ->
297
                case c of
298
                  False -> liftM Types.OpFail arbitrary
299
                  True -> liftM Types.OpGood arbitrary
300

    
301
-- * Actual tests
302

    
303
-- ** Utils tests
304

    
305
-- | If the list is not just an empty element, and if the elements do
306
-- not contain commas, then join+split should be idempotent.
307
prop_Utils_commaJoinSplit =
308
    forAll (arbitrary `suchThat`
309
            (\l -> l /= [""] && all (not . elem ',') l )) $ \lst ->
310
    Utils.sepSplit ',' (Utils.commaJoin lst) ==? lst
311

    
312
-- | Split and join should always be idempotent.
313
prop_Utils_commaSplitJoin s =
314
    Utils.commaJoin (Utils.sepSplit ',' s) ==? s
315

    
316
-- | fromObjWithDefault, we test using the Maybe monad and an integer
317
-- value.
318
prop_Utils_fromObjWithDefault def_value random_key =
319
    -- a missing key will be returned with the default
320
    Utils.fromObjWithDefault [] random_key def_value == Just def_value &&
321
    -- a found key will be returned as is, not with default
322
    Utils.fromObjWithDefault [(random_key, J.showJSON def_value)]
323
         random_key (def_value+1) == Just def_value
324
        where _types = def_value :: Integer
325

    
326
-- | Test that functional if' behaves like the syntactic sugar if.
327
prop_Utils_if'if :: Bool -> Int -> Int -> Gen Prop
328
prop_Utils_if'if cnd a b =
329
    Utils.if' cnd a b ==? if cnd then a else b
330

    
331
-- | Test basic select functionality
332
prop_Utils_select :: Int      -- ^ Default result
333
                  -> [Int]    -- ^ List of False values
334
                  -> [Int]    -- ^ List of True values
335
                  -> Gen Prop -- ^ Test result
336
prop_Utils_select def lst1 lst2 =
337
  Utils.select def cndlist ==? expectedresult
338
  where expectedresult = Utils.if' (null lst2) def (head lst2)
339
        flist = map (\e -> (False, e)) lst1
340
        tlist = map (\e -> (True, e)) lst2
341
        cndlist = flist ++ tlist
342

    
343
-- | Test basic select functionality with undefined default
344
prop_Utils_select_undefd :: [Int]            -- ^ List of False values
345
                         -> NonEmptyList Int -- ^ List of True values
346
                         -> Gen Prop         -- ^ Test result
347
prop_Utils_select_undefd lst1 (NonEmpty lst2) =
348
  Utils.select undefined cndlist ==? head lst2
349
  where flist = map (\e -> (False, e)) lst1
350
        tlist = map (\e -> (True, e)) lst2
351
        cndlist = flist ++ tlist
352

    
353
-- | Test basic select functionality with undefined list values
354
prop_Utils_select_undefv :: [Int]            -- ^ List of False values
355
                         -> NonEmptyList Int -- ^ List of True values
356
                         -> Gen Prop         -- ^ Test result
357
prop_Utils_select_undefv lst1 (NonEmpty lst2) =
358
  Utils.select undefined cndlist ==? head lst2
359
  where flist = map (\e -> (False, e)) lst1
360
        tlist = map (\e -> (True, e)) lst2
361
        cndlist = flist ++ tlist ++ [undefined]
362

    
363
prop_Utils_parseUnit (NonNegative n) =
364
    Utils.parseUnit (show n) == Types.Ok n &&
365
    Utils.parseUnit (show n ++ "m") == Types.Ok n &&
366
    (case Utils.parseUnit (show n ++ "M") of
367
      Types.Ok m -> if n > 0
368
                    then m < n  -- for positive values, X MB is less than X MiB
369
                    else m == 0 -- but for 0, 0 MB == 0 MiB
370
      Types.Bad _ -> False) &&
371
    Utils.parseUnit (show n ++ "g") == Types.Ok (n*1024) &&
372
    Utils.parseUnit (show n ++ "t") == Types.Ok (n*1048576) &&
373
    Types.isBad (Utils.parseUnit (show n ++ "x")::Types.Result Int)
374
    where _types = n::Int
375

    
376
-- | Test list for the Utils module.
377
testSuite "Utils"
378
              [ 'prop_Utils_commaJoinSplit
379
              , 'prop_Utils_commaSplitJoin
380
              , 'prop_Utils_fromObjWithDefault
381
              , 'prop_Utils_if'if
382
              , 'prop_Utils_select
383
              , 'prop_Utils_select_undefd
384
              , 'prop_Utils_select_undefv
385
              , 'prop_Utils_parseUnit
386
              ]
387

    
388
-- ** PeerMap tests
389

    
390
-- | Make sure add is idempotent.
391
prop_PeerMap_addIdempotent pmap key em =
392
    fn puniq ==? fn (fn puniq)
393
    where _types = (pmap::PeerMap.PeerMap,
394
                    key::PeerMap.Key, em::PeerMap.Elem)
395
          fn = PeerMap.add key em
396
          puniq = PeerMap.accumArray const pmap
397

    
398
-- | Make sure remove is idempotent.
399
prop_PeerMap_removeIdempotent pmap key =
400
    fn puniq ==? fn (fn puniq)
401
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
402
          fn = PeerMap.remove key
403
          puniq = PeerMap.accumArray const pmap
404

    
405
-- | Make sure a missing item returns 0.
406
prop_PeerMap_findMissing pmap key =
407
    PeerMap.find key (PeerMap.remove key puniq) ==? 0
408
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
409
          puniq = PeerMap.accumArray const pmap
410

    
411
-- | Make sure an added item is found.
412
prop_PeerMap_addFind pmap key em =
413
    PeerMap.find key (PeerMap.add key em puniq) ==? em
414
    where _types = (pmap::PeerMap.PeerMap,
415
                    key::PeerMap.Key, em::PeerMap.Elem)
416
          puniq = PeerMap.accumArray const pmap
417

    
418
-- | Manual check that maxElem returns the maximum indeed, or 0 for null.
419
prop_PeerMap_maxElem pmap =
420
    PeerMap.maxElem puniq ==? if null puniq then 0
421
                              else (maximum . snd . unzip) puniq
422
    where _types = pmap::PeerMap.PeerMap
423
          puniq = PeerMap.accumArray const pmap
424

    
425
-- | List of tests for the PeerMap module.
426
testSuite "PeerMap"
427
              [ 'prop_PeerMap_addIdempotent
428
              , 'prop_PeerMap_removeIdempotent
429
              , 'prop_PeerMap_maxElem
430
              , 'prop_PeerMap_addFind
431
              , 'prop_PeerMap_findMissing
432
              ]
433

    
434
-- ** Container tests
435

    
436
prop_Container_addTwo cdata i1 i2 =
437
    fn i1 i2 cont == fn i2 i1 cont &&
438
       fn i1 i2 cont == fn i1 i2 (fn i1 i2 cont)
439
    where _types = (cdata::[Int],
440
                    i1::Int, i2::Int)
441
          cont = foldl (\c x -> Container.add x x c) Container.empty cdata
442
          fn x1 x2 = Container.addTwo x1 x1 x2 x2
443

    
444
prop_Container_nameOf node =
445
  let nl = makeSmallCluster node 1
446
      fnode = head (Container.elems nl)
447
  in Container.nameOf nl (Node.idx fnode) ==? Node.name fnode
448

    
449
-- | We test that in a cluster, given a random node, we can find it by
450
-- its name and alias, as long as all names and aliases are unique,
451
-- and that we fail to find a non-existing name.
452
prop_Container_findByName node othername =
453
  forAll (choose (1, 20)) $ \ cnt ->
454
  forAll (choose (0, cnt - 1)) $ \ fidx ->
455
  forAll (vector cnt) $ \ names ->
456
  (length . nub) (map fst names ++ map snd names) ==
457
  length names * 2 &&
458
  not (othername `elem` (map fst names ++ map snd names)) ==>
459
  let nl = makeSmallCluster node cnt
460
      nodes = Container.elems nl
461
      nodes' = map (\((name, alias), nn) -> (Node.idx nn,
462
                                             nn { Node.name = name,
463
                                                  Node.alias = alias }))
464
               $ zip names nodes
465
      nl' = Container.fromList nodes'
466
      target = snd (nodes' !! fidx)
467
  in Container.findByName nl' (Node.name target) == Just target &&
468
     Container.findByName nl' (Node.alias target) == Just target &&
469
     Container.findByName nl' othername == Nothing
470

    
471
testSuite "Container"
472
              [ 'prop_Container_addTwo
473
              , 'prop_Container_nameOf
474
              , 'prop_Container_findByName
475
              ]
476

    
477
-- ** Instance tests
478

    
479
-- Simple instance tests, we only have setter/getters
480

    
481
prop_Instance_creat inst =
482
    Instance.name inst ==? Instance.alias inst
483

    
484
prop_Instance_setIdx inst idx =
485
    Instance.idx (Instance.setIdx inst idx) ==? idx
486
    where _types = (inst::Instance.Instance, idx::Types.Idx)
487

    
488
prop_Instance_setName inst name =
489
    Instance.name newinst == name &&
490
    Instance.alias newinst == name
491
    where _types = (inst::Instance.Instance, name::String)
492
          newinst = Instance.setName inst name
493

    
494
prop_Instance_setAlias inst name =
495
    Instance.name newinst == Instance.name inst &&
496
    Instance.alias newinst == name
497
    where _types = (inst::Instance.Instance, name::String)
498
          newinst = Instance.setAlias inst name
499

    
500
prop_Instance_setPri inst pdx =
501
    Instance.pNode (Instance.setPri inst pdx) ==? pdx
502
    where _types = (inst::Instance.Instance, pdx::Types.Ndx)
503

    
504
prop_Instance_setSec inst sdx =
505
    Instance.sNode (Instance.setSec inst sdx) ==? sdx
506
    where _types = (inst::Instance.Instance, sdx::Types.Ndx)
507

    
508
prop_Instance_setBoth inst pdx sdx =
509
    Instance.pNode si == pdx && Instance.sNode si == sdx
510
    where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
511
          si = Instance.setBoth inst pdx sdx
512

    
513
prop_Instance_runStatus_True =
514
    forAll (arbitrary `suchThat`
515
            ((`elem` Instance.runningStates) . Instance.runSt))
516
    Instance.running
517

    
518
prop_Instance_runStatus_False inst =
519
    let run_st = Instance.running inst
520
        run_tx = Instance.runSt inst
521
    in
522
      run_tx `notElem` Instance.runningStates ==> not run_st
523

    
524
prop_Instance_shrinkMG inst =
525
    Instance.mem inst >= 2 * Types.unitMem ==>
526
        case Instance.shrinkByType inst Types.FailMem of
527
          Types.Ok inst' ->
528
              Instance.mem inst' == Instance.mem inst - Types.unitMem
529
          _ -> False
530

    
531
prop_Instance_shrinkMF inst =
532
    forAll (choose (0, 2 * Types.unitMem - 1)) $ \mem ->
533
    let inst' = inst { Instance.mem = mem}
534
    in Types.isBad $ Instance.shrinkByType inst' Types.FailMem
535

    
536
prop_Instance_shrinkCG inst =
537
    Instance.vcpus inst >= 2 * Types.unitCpu ==>
538
        case Instance.shrinkByType inst Types.FailCPU of
539
          Types.Ok inst' ->
540
              Instance.vcpus inst' == Instance.vcpus inst - Types.unitCpu
541
          _ -> False
542

    
543
prop_Instance_shrinkCF inst =
544
    forAll (choose (0, 2 * Types.unitCpu - 1)) $ \vcpus ->
545
    let inst' = inst { Instance.vcpus = vcpus }
546
    in Types.isBad $ Instance.shrinkByType inst' Types.FailCPU
547

    
548
prop_Instance_shrinkDG inst =
549
    Instance.dsk inst >= 2 * Types.unitDsk ==>
550
        case Instance.shrinkByType inst Types.FailDisk of
551
          Types.Ok inst' ->
552
              Instance.dsk inst' == Instance.dsk inst - Types.unitDsk
553
          _ -> False
554

    
555
prop_Instance_shrinkDF inst =
556
    forAll (choose (0, 2 * Types.unitDsk - 1)) $ \dsk ->
557
    let inst' = inst { Instance.dsk = dsk }
558
    in Types.isBad $ Instance.shrinkByType inst' Types.FailDisk
559

    
560
prop_Instance_setMovable inst m =
561
    Instance.movable inst' ==? m
562
    where inst' = Instance.setMovable inst m
563

    
564
testSuite "Instance"
565
              [ 'prop_Instance_creat
566
              , 'prop_Instance_setIdx
567
              , 'prop_Instance_setName
568
              , 'prop_Instance_setAlias
569
              , 'prop_Instance_setPri
570
              , 'prop_Instance_setSec
571
              , 'prop_Instance_setBoth
572
              , 'prop_Instance_runStatus_True
573
              , 'prop_Instance_runStatus_False
574
              , 'prop_Instance_shrinkMG
575
              , 'prop_Instance_shrinkMF
576
              , 'prop_Instance_shrinkCG
577
              , 'prop_Instance_shrinkCF
578
              , 'prop_Instance_shrinkDG
579
              , 'prop_Instance_shrinkDF
580
              , 'prop_Instance_setMovable
581
              ]
582

    
583
-- ** Text backend tests
584

    
585
-- Instance text loader tests
586

    
587
prop_Text_Load_Instance name mem dsk vcpus status
588
                        (NonEmpty pnode) snode
589
                        (NonNegative pdx) (NonNegative sdx) autobal dt =
590
    pnode /= snode && pdx /= sdx ==>
591
    let vcpus_s = show vcpus
592
        dsk_s = show dsk
593
        mem_s = show mem
594
        ndx = if null snode
595
              then [(pnode, pdx)]
596
              else [(pnode, pdx), (snode, sdx)]
597
        nl = Data.Map.fromList ndx
598
        tags = ""
599
        sbal = if autobal then "Y" else "N"
600
        sdt = Types.diskTemplateToRaw dt
601
        inst = Text.loadInst nl
602
               [name, mem_s, dsk_s, vcpus_s, status,
603
                sbal, pnode, snode, sdt, tags]
604
        fail1 = Text.loadInst nl
605
               [name, mem_s, dsk_s, vcpus_s, status,
606
                sbal, pnode, pnode, tags]
607
        _types = ( name::String, mem::Int, dsk::Int
608
                 , vcpus::Int, status::String
609
                 , snode::String
610
                 , autobal::Bool)
611
    in
612
      case inst of
613
        Types.Bad msg -> printTestCase ("Failed to load instance: " ++ msg)
614
                         False
615
        Types.Ok (_, i) -> printTestCase "Mismatch in some field while\
616
                                         \ loading the instance" $
617
            Instance.name i == name &&
618
            Instance.vcpus i == vcpus &&
619
            Instance.mem i == mem &&
620
            Instance.pNode i == pdx &&
621
            Instance.sNode i == (if null snode
622
                                 then Node.noSecondary
623
                                 else sdx) &&
624
            Instance.autoBalance i == autobal &&
625
            Types.isBad fail1
626

    
627
prop_Text_Load_InstanceFail ktn fields =
628
    length fields /= 10 ==>
629
    case Text.loadInst nl fields of
630
      Types.Ok _ -> printTestCase "Managed to load instance from invalid\
631
                                  \ data" False
632
      Types.Bad msg -> printTestCase ("Unrecognised error message: " ++ msg) $
633
                       "Invalid/incomplete instance data: '" `isPrefixOf` msg
634
    where nl = Data.Map.fromList ktn
635

    
636
prop_Text_Load_Node name tm nm fm td fd tc fo =
637
    let conv v = if v < 0
638
                    then "?"
639
                    else show v
640
        tm_s = conv tm
641
        nm_s = conv nm
642
        fm_s = conv fm
643
        td_s = conv td
644
        fd_s = conv fd
645
        tc_s = conv tc
646
        fo_s = if fo
647
               then "Y"
648
               else "N"
649
        any_broken = any (< 0) [tm, nm, fm, td, fd, tc]
650
        gid = Group.uuid defGroup
651
    in case Text.loadNode defGroupAssoc
652
           [name, tm_s, nm_s, fm_s, td_s, fd_s, tc_s, fo_s, gid] of
653
         Nothing -> False
654
         Just (name', node) ->
655
             if fo || any_broken
656
             then Node.offline node
657
             else Node.name node == name' && name' == name &&
658
                  Node.alias node == name &&
659
                  Node.tMem node == fromIntegral tm &&
660
                  Node.nMem node == nm &&
661
                  Node.fMem node == fm &&
662
                  Node.tDsk node == fromIntegral td &&
663
                  Node.fDsk node == fd &&
664
                  Node.tCpu node == fromIntegral tc
665

    
666
prop_Text_Load_NodeFail fields =
667
    length fields /= 8 ==> isNothing $ Text.loadNode Data.Map.empty fields
668

    
669
prop_Text_NodeLSIdempotent node =
670
    (Text.loadNode defGroupAssoc.
671
         Utils.sepSplit '|' . Text.serializeNode defGroupList) n ==
672
    Just (Node.name n, n)
673
    -- override failN1 to what loadNode returns by default
674
    where n = node { Node.failN1 = True, Node.offline = False }
675

    
676
testSuite "Text"
677
              [ 'prop_Text_Load_Instance
678
              , 'prop_Text_Load_InstanceFail
679
              , 'prop_Text_Load_Node
680
              , 'prop_Text_Load_NodeFail
681
              , 'prop_Text_NodeLSIdempotent
682
              ]
683

    
684
-- ** Node tests
685

    
686
prop_Node_setAlias node name =
687
    Node.name newnode == Node.name node &&
688
    Node.alias newnode == name
689
    where _types = (node::Node.Node, name::String)
690
          newnode = Node.setAlias node name
691

    
692
prop_Node_setOffline node status =
693
    Node.offline newnode ==? status
694
    where newnode = Node.setOffline node status
695

    
696
prop_Node_setXmem node xm =
697
    Node.xMem newnode ==? xm
698
    where newnode = Node.setXmem node xm
699

    
700
prop_Node_setMcpu node mc =
701
    Node.mCpu newnode ==? mc
702
    where newnode = Node.setMcpu node mc
703

    
704
-- | Check that an instance add with too high memory or disk will be
705
-- rejected.
706
prop_Node_addPriFM node inst = Instance.mem inst >= Node.fMem node &&
707
                               not (Node.failN1 node)
708
                               ==>
709
                               case Node.addPri node inst'' of
710
                                 Types.OpFail Types.FailMem -> True
711
                                 _ -> False
712
    where _types = (node::Node.Node, inst::Instance.Instance)
713
          inst' = setInstanceSmallerThanNode node inst
714
          inst'' = inst' { Instance.mem = Instance.mem inst }
715

    
716
prop_Node_addPriFD node inst = Instance.dsk inst >= Node.fDsk node &&
717
                               not (Node.failN1 node)
718
                               ==>
719
                               case Node.addPri node inst'' of
720
                                 Types.OpFail Types.FailDisk -> True
721
                                 _ -> False
722
    where _types = (node::Node.Node, inst::Instance.Instance)
723
          inst' = setInstanceSmallerThanNode node inst
724
          inst'' = inst' { Instance.dsk = Instance.dsk inst }
725

    
726
prop_Node_addPriFC node inst (Positive extra) =
727
    not (Node.failN1 node) ==>
728
        case Node.addPri node inst'' of
729
          Types.OpFail Types.FailCPU -> True
730
          _ -> False
731
    where _types = (node::Node.Node, inst::Instance.Instance)
732
          inst' = setInstanceSmallerThanNode node inst
733
          inst'' = inst' { Instance.vcpus = Node.availCpu node + extra }
734

    
735
-- | Check that an instance add with too high memory or disk will be
736
-- rejected.
737
prop_Node_addSec node inst pdx =
738
    (Instance.mem inst >= (Node.fMem node - Node.rMem node) ||
739
     Instance.dsk inst >= Node.fDsk node) &&
740
    not (Node.failN1 node)
741
    ==> isFailure (Node.addSec node inst pdx)
742
        where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
743

    
744
-- | Checks for memory reservation changes.
745
prop_Node_rMem inst =
746
    forAll (arbitrary `suchThat` ((> Types.unitMem) . Node.fMem)) $ \node ->
747
    -- ab = auto_balance, nb = non-auto_balance
748
    -- we use -1 as the primary node of the instance
749
    let inst' = inst { Instance.pNode = -1, Instance.autoBalance = True }
750
        inst_ab = setInstanceSmallerThanNode node inst'
751
        inst_nb = inst_ab { Instance.autoBalance = False }
752
        -- now we have the two instances, identical except the
753
        -- autoBalance attribute
754
        orig_rmem = Node.rMem node
755
        inst_idx = Instance.idx inst_ab
756
        node_add_ab = Node.addSec node inst_ab (-1)
757
        node_add_nb = Node.addSec node inst_nb (-1)
758
        node_del_ab = liftM (`Node.removeSec` inst_ab) node_add_ab
759
        node_del_nb = liftM (`Node.removeSec` inst_nb) node_add_nb
760
    in case (node_add_ab, node_add_nb, node_del_ab, node_del_nb) of
761
         (Types.OpGood a_ab, Types.OpGood a_nb,
762
          Types.OpGood d_ab, Types.OpGood d_nb) ->
763
             printTestCase "Consistency checks failed" $
764
             Node.rMem a_ab >  orig_rmem &&
765
             Node.rMem a_ab - orig_rmem == Instance.mem inst_ab &&
766
             Node.rMem a_nb == orig_rmem &&
767
             Node.rMem d_ab == orig_rmem &&
768
             Node.rMem d_nb == orig_rmem &&
769
             -- this is not related to rMem, but as good a place to
770
             -- test as any
771
             inst_idx `elem` Node.sList a_ab &&
772
             not (inst_idx `elem` Node.sList d_ab)
773
         x -> printTestCase ("Failed to add/remove instances: " ++ show x)
774
              False
775

    
776
-- | Check mdsk setting.
777
prop_Node_setMdsk node mx =
778
    Node.loDsk node' >= 0 &&
779
    fromIntegral (Node.loDsk node') <= Node.tDsk node &&
780
    Node.availDisk node' >= 0 &&
781
    Node.availDisk node' <= Node.fDsk node' &&
782
    fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
783
    Node.mDsk node' == mx'
784
    where _types = (node::Node.Node, mx::SmallRatio)
785
          node' = Node.setMdsk node mx'
786
          SmallRatio mx' = mx
787

    
788
-- Check tag maps
789
prop_Node_tagMaps_idempotent tags =
790
    Node.delTags (Node.addTags m tags) tags ==? m
791
    where m = Data.Map.empty
792

    
793
prop_Node_tagMaps_reject tags =
794
    not (null tags) ==>
795
    all (\t -> Node.rejectAddTags m [t]) tags
796
    where m = Node.addTags Data.Map.empty tags
797

    
798
prop_Node_showField node =
799
  forAll (elements Node.defaultFields) $ \ field ->
800
  fst (Node.showHeader field) /= Types.unknownField &&
801
  Node.showField node field /= Types.unknownField
802

    
803
prop_Node_computeGroups nodes =
804
  let ng = Node.computeGroups nodes
805
      onlyuuid = map fst ng
806
  in length nodes == sum (map (length . snd) ng) &&
807
     all (\(guuid, ns) -> all ((== guuid) . Node.group) ns) ng &&
808
     length (nub onlyuuid) == length onlyuuid &&
809
     (null nodes || not (null ng))
810

    
811
testSuite "Node"
812
              [ 'prop_Node_setAlias
813
              , 'prop_Node_setOffline
814
              , 'prop_Node_setMcpu
815
              , 'prop_Node_setXmem
816
              , 'prop_Node_addPriFM
817
              , 'prop_Node_addPriFD
818
              , 'prop_Node_addPriFC
819
              , 'prop_Node_addSec
820
              , 'prop_Node_rMem
821
              , 'prop_Node_setMdsk
822
              , 'prop_Node_tagMaps_idempotent
823
              , 'prop_Node_tagMaps_reject
824
              , 'prop_Node_showField
825
              , 'prop_Node_computeGroups
826
              ]
827

    
828
-- ** Cluster tests
829

    
830
-- | Check that the cluster score is close to zero for a homogeneous
831
-- cluster.
832
prop_Score_Zero node =
833
    forAll (choose (1, 1024)) $ \count ->
834
    (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
835
     (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
836
    let fn = Node.buildPeers node Container.empty
837
        nlst = replicate count fn
838
        score = Cluster.compCVNodes nlst
839
    -- we can't say == 0 here as the floating point errors accumulate;
840
    -- this should be much lower than the default score in CLI.hs
841
    in score <= 1e-12
842

    
843
-- | Check that cluster stats are sane.
844
prop_CStats_sane node =
845
    forAll (choose (1, 1024)) $ \count ->
846
    (not (Node.offline node) && not (Node.failN1 node) &&
847
     (Node.availDisk node > 0) && (Node.availMem node > 0)) ==>
848
    let fn = Node.buildPeers node Container.empty
849
        nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
850
        nl = Container.fromList nlst
851
        cstats = Cluster.totalResources nl
852
    in Cluster.csAdsk cstats >= 0 &&
853
       Cluster.csAdsk cstats <= Cluster.csFdsk cstats
854

    
855
-- | Check that one instance is allocated correctly, without
856
-- rebalances needed.
857
prop_ClusterAlloc_sane node inst =
858
    forAll (choose (5, 20)) $ \count ->
859
    not (Node.offline node)
860
            && not (Node.failN1 node)
861
            && Node.availDisk node > 0
862
            && Node.availMem node > 0
863
            ==>
864
    let nl = makeSmallCluster node count
865
        il = Container.empty
866
        inst' = setInstanceSmallerThanNode node inst
867
    in case Cluster.genAllocNodes defGroupList nl 2 True >>=
868
       Cluster.tryAlloc nl il inst' of
869
         Types.Bad _ -> False
870
         Types.Ok as ->
871
             case Cluster.asSolution as of
872
               Nothing -> False
873
               Just (xnl, xi, _, cv) ->
874
                   let il' = Container.add (Instance.idx xi) xi il
875
                       tbl = Cluster.Table xnl il' cv []
876
                   in not (canBalance tbl True True False)
877

    
878
-- | Checks that on a 2-5 node cluster, we can allocate a random
879
-- instance spec via tiered allocation (whatever the original instance
880
-- spec), on either one or two nodes.
881
prop_ClusterCanTieredAlloc node inst =
882
    forAll (choose (2, 5)) $ \count ->
883
    forAll (choose (1, 2)) $ \rqnodes ->
884
    not (Node.offline node)
885
            && not (Node.failN1 node)
886
            && isNodeBig node 4
887
            ==>
888
    let nl = makeSmallCluster node count
889
        il = Container.empty
890
        allocnodes = Cluster.genAllocNodes defGroupList nl rqnodes True
891
    in case allocnodes >>= \allocnodes' ->
892
        Cluster.tieredAlloc nl il (Just 1) inst allocnodes' [] [] of
893
         Types.Bad _ -> False
894
         Types.Ok (_, _, il', ixes, cstats) -> not (null ixes) &&
895
                                      IntMap.size il' == length ixes &&
896
                                      length ixes == length cstats
897

    
898
-- | Checks that on a 4-8 node cluster, once we allocate an instance,
899
-- we can also evacuate it.
900
prop_ClusterAllocEvac node inst =
901
    forAll (choose (4, 8)) $ \count ->
902
    not (Node.offline node)
903
            && not (Node.failN1 node)
904
            && isNodeBig node 4
905
            ==>
906
    let nl = makeSmallCluster node count
907
        il = Container.empty
908
        inst' = setInstanceSmallerThanNode node inst
909
    in case Cluster.genAllocNodes defGroupList nl 2 True >>=
910
       Cluster.tryAlloc nl il inst' of
911
         Types.Bad _ -> False
912
         Types.Ok as ->
913
             case Cluster.asSolution as of
914
               Nothing -> False
915
               Just (xnl, xi, _, _) ->
916
                   let sdx = Instance.sNode xi
917
                       il' = Container.add (Instance.idx xi) xi il
918
                   in case IAlloc.processRelocate defGroupList xnl il'
919
                          (Instance.idx xi) 1 [sdx] of
920
                        Types.Ok _ -> True
921
                        _ -> False
922

    
923
-- | Check that allocating multiple instances on a cluster, then
924
-- adding an empty node, results in a valid rebalance.
925
prop_ClusterAllocBalance =
926
    forAll (genNode (Just 5) (Just 128)) $ \node ->
927
    forAll (choose (3, 5)) $ \count ->
928
    not (Node.offline node) && not (Node.failN1 node) ==>
929
    let nl = makeSmallCluster node count
930
        (hnode, nl') = IntMap.deleteFindMax nl
931
        il = Container.empty
932
        allocnodes = Cluster.genAllocNodes defGroupList nl' 2 True
933
        i_templ = createInstance Types.unitMem Types.unitDsk Types.unitCpu
934
    in case allocnodes >>= \allocnodes' ->
935
        Cluster.iterateAlloc nl' il (Just 5) i_templ allocnodes' [] [] of
936
         Types.Bad _ -> False
937
         Types.Ok (_, xnl, il', _, _) ->
938
                   let ynl = Container.add (Node.idx hnode) hnode xnl
939
                       cv = Cluster.compCV ynl
940
                       tbl = Cluster.Table ynl il' cv []
941
                   in canBalance tbl True True False
942

    
943
-- | Checks consistency.
944
prop_ClusterCheckConsistency node inst =
945
  let nl = makeSmallCluster node 3
946
      [node1, node2, node3] = Container.elems nl
947
      node3' = node3 { Node.group = 1 }
948
      nl' = Container.add (Node.idx node3') node3' nl
949
      inst1 = Instance.setBoth inst (Node.idx node1) (Node.idx node2)
950
      inst2 = Instance.setBoth inst (Node.idx node1) Node.noSecondary
951
      inst3 = Instance.setBoth inst (Node.idx node1) (Node.idx node3)
952
      ccheck = Cluster.findSplitInstances nl' . Container.fromList
953
  in null (ccheck [(0, inst1)]) &&
954
     null (ccheck [(0, inst2)]) &&
955
     (not . null $ ccheck [(0, inst3)])
956

    
957
-- | For now, we only test that we don't lose instances during the split.
958
prop_ClusterSplitCluster node inst =
959
  forAll (choose (0, 100)) $ \icnt ->
960
  let nl = makeSmallCluster node 2
961
      (nl', il') = foldl (\(ns, is) _ -> assignInstance ns is inst 0 1)
962
                   (nl, Container.empty) [1..icnt]
963
      gni = Cluster.splitCluster nl' il'
964
  in sum (map (Container.size . snd . snd) gni) == icnt &&
965
     all (\(guuid, (nl'', _)) -> all ((== guuid) . Node.group)
966
                                 (Container.elems nl'')) gni
967

    
968
testSuite "Cluster"
969
              [ 'prop_Score_Zero
970
              , 'prop_CStats_sane
971
              , 'prop_ClusterAlloc_sane
972
              , 'prop_ClusterCanTieredAlloc
973
              , 'prop_ClusterAllocEvac
974
              , 'prop_ClusterAllocBalance
975
              , 'prop_ClusterCheckConsistency
976
              , 'prop_ClusterSplitCluster
977
              ]
978

    
979
-- ** OpCodes tests
980

    
981
-- | Check that opcode serialization is idempotent.
982
prop_OpCodes_serialization op =
983
  case J.readJSON (J.showJSON op) of
984
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
985
    J.Ok op' -> op ==? op'
986
  where _types = op::OpCodes.OpCode
987

    
988
testSuite "OpCodes"
989
              [ 'prop_OpCodes_serialization ]
990

    
991
-- ** Jobs tests
992

    
993
-- | Check that (queued) job\/opcode status serialization is idempotent.
994
prop_OpStatus_serialization os =
995
  case J.readJSON (J.showJSON os) of
996
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
997
    J.Ok os' -> os ==? os'
998
  where _types = os::Jobs.OpStatus
999

    
1000
prop_JobStatus_serialization js =
1001
  case J.readJSON (J.showJSON js) of
1002
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
1003
    J.Ok js' -> js ==? js'
1004
  where _types = js::Jobs.JobStatus
1005

    
1006
testSuite "Jobs"
1007
              [ 'prop_OpStatus_serialization
1008
              , 'prop_JobStatus_serialization
1009
              ]
1010

    
1011
-- ** Loader tests
1012

    
1013
prop_Loader_lookupNode ktn inst node =
1014
  Loader.lookupNode nl inst node ==? Data.Map.lookup node nl
1015
  where nl = Data.Map.fromList ktn
1016

    
1017
prop_Loader_lookupInstance kti inst =
1018
  Loader.lookupInstance il inst ==? Data.Map.lookup inst il
1019
  where il = Data.Map.fromList kti
1020

    
1021
prop_Loader_assignIndices nodes =
1022
  Data.Map.size nassoc == length nodes &&
1023
  Container.size kt == length nodes &&
1024
  (if not (null nodes)
1025
   then maximum (IntMap.keys kt) == length nodes - 1
1026
   else True)
1027
  where (nassoc, kt) = Loader.assignIndices (map (\n -> (Node.name n, n)) nodes)
1028

    
1029
-- | Checks that the number of primary instances recorded on the nodes
1030
-- is zero.
1031
prop_Loader_mergeData ns =
1032
  let na = Container.fromList $ map (\n -> (Node.idx n, n)) ns
1033
  in case Loader.mergeData [] [] [] []
1034
         (Loader.emptyCluster {Loader.cdNodes = na}) of
1035
    Types.Bad _ -> False
1036
    Types.Ok (Loader.ClusterData _ nl il _) ->
1037
      let nodes = Container.elems nl
1038
          instances = Container.elems il
1039
      in (sum . map (length . Node.pList)) nodes == 0 &&
1040
         null instances
1041

    
1042
-- | Check that compareNameComponent on equal strings works.
1043
prop_Loader_compareNameComponent_equal :: String -> Bool
1044
prop_Loader_compareNameComponent_equal s =
1045
  Loader.compareNameComponent s s ==
1046
    Loader.LookupResult Loader.ExactMatch s
1047

    
1048
-- | Check that compareNameComponent on prefix strings works.
1049
prop_Loader_compareNameComponent_prefix :: NonEmptyList Char -> String -> Bool
1050
prop_Loader_compareNameComponent_prefix (NonEmpty s1) s2 =
1051
  Loader.compareNameComponent (s1 ++ "." ++ s2) s1 ==
1052
    Loader.LookupResult Loader.PartialMatch s1
1053

    
1054
testSuite "Loader"
1055
              [ 'prop_Loader_lookupNode
1056
              , 'prop_Loader_lookupInstance
1057
              , 'prop_Loader_assignIndices
1058
              , 'prop_Loader_mergeData
1059
              , 'prop_Loader_compareNameComponent_equal
1060
              , 'prop_Loader_compareNameComponent_prefix
1061
              ]
1062

    
1063
-- ** Types tests
1064

    
1065
prop_Types_AllocPolicy_serialisation apol =
1066
    case J.readJSON (J.showJSON apol) of
1067
      J.Ok p -> printTestCase ("invalid deserialisation " ++ show p) $
1068
                p == apol
1069
      J.Error s -> printTestCase ("failed to deserialise: " ++ s) False
1070
    where _types = apol::Types.AllocPolicy
1071

    
1072
prop_Types_DiskTemplate_serialisation dt =
1073
    case J.readJSON (J.showJSON dt) of
1074
      J.Ok p -> printTestCase ("invalid deserialisation " ++ show p) $
1075
                p == dt
1076
      J.Error s -> printTestCase ("failed to deserialise: " ++ s)
1077
                   False
1078
    where _types = dt::Types.DiskTemplate
1079

    
1080
prop_Types_opToResult op =
1081
    case op of
1082
      Types.OpFail _ -> Types.isBad r
1083
      Types.OpGood v -> case r of
1084
                          Types.Bad _ -> False
1085
                          Types.Ok v' -> v == v'
1086
    where r = Types.opToResult op
1087
          _types = op::Types.OpResult Int
1088

    
1089
prop_Types_eitherToResult ei =
1090
    case ei of
1091
      Left _ -> Types.isBad r
1092
      Right v -> case r of
1093
                   Types.Bad _ -> False
1094
                   Types.Ok v' -> v == v'
1095
    where r = Types.eitherToResult ei
1096
          _types = ei::Either String Int
1097

    
1098
testSuite "Types"
1099
              [ 'prop_Types_AllocPolicy_serialisation
1100
              , 'prop_Types_DiskTemplate_serialisation
1101
              , 'prop_Types_opToResult
1102
              , 'prop_Types_eitherToResult
1103
              ]