Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / QC.hs @ 61bbbed7

History | View | Annotate | Download (40.6 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 Types.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
instance Arbitrary Types.InstanceStatus where
191
    arbitrary = elements [ Types.AdminDown
192
                         , Types.AdminOffline
193
                         , Types.ErrorDown
194
                         , Types.ErrorUp
195
                         , Types.NodeDown
196
                         , Types.NodeOffline
197
                         , Types.Running
198
                         , Types.WrongNode]
199

    
200
-- let's generate a random instance
201
instance Arbitrary Instance.Instance where
202
    arbitrary = do
203
      name <- getFQDN
204
      mem <- choose (0, maxMem)
205
      dsk <- choose (0, maxDsk)
206
      run_st <- arbitrary
207
      pn <- arbitrary
208
      sn <- arbitrary
209
      vcpus <- choose (0, maxCpu)
210
      return $ Instance.create name mem dsk vcpus run_st [] True pn sn
211
                               Types.DTDrbd8
212

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

    
243
-- and a random node
244
instance Arbitrary Node.Node where
245
    arbitrary = genNode Nothing Nothing
246

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

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

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

    
280
instance Arbitrary Jobs.JobStatus where
281
  arbitrary = elements [minBound..maxBound]
282

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

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

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

    
295
instance Arbitrary Types.FailMode where
296
    arbitrary = elements [minBound..maxBound]
297

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

    
304
-- * Actual tests
305

    
306
-- ** Utils tests
307

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

    
315
-- | Split and join should always be idempotent.
316
prop_Utils_commaSplitJoin s =
317
    Utils.commaJoin (Utils.sepSplit ',' s) ==? s
318

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

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

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

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

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

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

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

    
391
-- ** PeerMap tests
392

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

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

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

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

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

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

    
437
-- ** Container tests
438

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

    
447
prop_Container_nameOf node =
448
  let nl = makeSmallCluster node 1
449
      fnode = head (Container.elems nl)
450
  in Container.nameOf nl (Node.idx fnode) ==? Node.name fnode
451

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

    
474
testSuite "Container"
475
              [ 'prop_Container_addTwo
476
              , 'prop_Container_nameOf
477
              , 'prop_Container_findByName
478
              ]
479

    
480
-- ** Instance tests
481

    
482
-- Simple instance tests, we only have setter/getters
483

    
484
prop_Instance_creat inst =
485
    Instance.name inst ==? Instance.alias inst
486

    
487
prop_Instance_setIdx inst idx =
488
    Instance.idx (Instance.setIdx inst idx) ==? idx
489
    where _types = (inst::Instance.Instance, idx::Types.Idx)
490

    
491
prop_Instance_setName inst name =
492
    Instance.name newinst == name &&
493
    Instance.alias newinst == name
494
    where _types = (inst::Instance.Instance, name::String)
495
          newinst = Instance.setName inst name
496

    
497
prop_Instance_setAlias inst name =
498
    Instance.name newinst == Instance.name inst &&
499
    Instance.alias newinst == name
500
    where _types = (inst::Instance.Instance, name::String)
501
          newinst = Instance.setAlias inst name
502

    
503
prop_Instance_setPri inst pdx =
504
    Instance.pNode (Instance.setPri inst pdx) ==? pdx
505
    where _types = (inst::Instance.Instance, pdx::Types.Ndx)
506

    
507
prop_Instance_setSec inst sdx =
508
    Instance.sNode (Instance.setSec inst sdx) ==? sdx
509
    where _types = (inst::Instance.Instance, sdx::Types.Ndx)
510

    
511
prop_Instance_setBoth inst pdx sdx =
512
    Instance.pNode si == pdx && Instance.sNode si == sdx
513
    where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
514
          si = Instance.setBoth inst pdx sdx
515

    
516
prop_Instance_shrinkMG inst =
517
    Instance.mem inst >= 2 * Types.unitMem ==>
518
        case Instance.shrinkByType inst Types.FailMem of
519
          Types.Ok inst' ->
520
              Instance.mem inst' == Instance.mem inst - Types.unitMem
521
          _ -> False
522

    
523
prop_Instance_shrinkMF inst =
524
    forAll (choose (0, 2 * Types.unitMem - 1)) $ \mem ->
525
    let inst' = inst { Instance.mem = mem}
526
    in Types.isBad $ Instance.shrinkByType inst' Types.FailMem
527

    
528
prop_Instance_shrinkCG inst =
529
    Instance.vcpus inst >= 2 * Types.unitCpu ==>
530
        case Instance.shrinkByType inst Types.FailCPU of
531
          Types.Ok inst' ->
532
              Instance.vcpus inst' == Instance.vcpus inst - Types.unitCpu
533
          _ -> False
534

    
535
prop_Instance_shrinkCF inst =
536
    forAll (choose (0, 2 * Types.unitCpu - 1)) $ \vcpus ->
537
    let inst' = inst { Instance.vcpus = vcpus }
538
    in Types.isBad $ Instance.shrinkByType inst' Types.FailCPU
539

    
540
prop_Instance_shrinkDG inst =
541
    Instance.dsk inst >= 2 * Types.unitDsk ==>
542
        case Instance.shrinkByType inst Types.FailDisk of
543
          Types.Ok inst' ->
544
              Instance.dsk inst' == Instance.dsk inst - Types.unitDsk
545
          _ -> False
546

    
547
prop_Instance_shrinkDF inst =
548
    forAll (choose (0, 2 * Types.unitDsk - 1)) $ \dsk ->
549
    let inst' = inst { Instance.dsk = dsk }
550
    in Types.isBad $ Instance.shrinkByType inst' Types.FailDisk
551

    
552
prop_Instance_setMovable inst m =
553
    Instance.movable inst' ==? m
554
    where inst' = Instance.setMovable inst m
555

    
556
testSuite "Instance"
557
              [ 'prop_Instance_creat
558
              , 'prop_Instance_setIdx
559
              , 'prop_Instance_setName
560
              , 'prop_Instance_setAlias
561
              , 'prop_Instance_setPri
562
              , 'prop_Instance_setSec
563
              , 'prop_Instance_setBoth
564
              , 'prop_Instance_shrinkMG
565
              , 'prop_Instance_shrinkMF
566
              , 'prop_Instance_shrinkCG
567
              , 'prop_Instance_shrinkCF
568
              , 'prop_Instance_shrinkDG
569
              , 'prop_Instance_shrinkDF
570
              , 'prop_Instance_setMovable
571
              ]
572

    
573
-- ** Text backend tests
574

    
575
-- Instance text loader tests
576

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

    
618
prop_Text_Load_InstanceFail ktn fields =
619
    length fields /= 10 ==>
620
    case Text.loadInst nl fields of
621
      Types.Ok _ -> printTestCase "Managed to load instance from invalid\
622
                                  \ data" False
623
      Types.Bad msg -> printTestCase ("Unrecognised error message: " ++ msg) $
624
                       "Invalid/incomplete instance data: '" `isPrefixOf` msg
625
    where nl = Data.Map.fromList ktn
626

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

    
657
prop_Text_Load_NodeFail fields =
658
    length fields /= 8 ==> isNothing $ Text.loadNode Data.Map.empty fields
659

    
660
prop_Text_NodeLSIdempotent node =
661
    (Text.loadNode defGroupAssoc.
662
         Utils.sepSplit '|' . Text.serializeNode defGroupList) n ==
663
    Just (Node.name n, n)
664
    -- override failN1 to what loadNode returns by default
665
    where n = node { Node.failN1 = True, Node.offline = False }
666

    
667
testSuite "Text"
668
              [ 'prop_Text_Load_Instance
669
              , 'prop_Text_Load_InstanceFail
670
              , 'prop_Text_Load_Node
671
              , 'prop_Text_Load_NodeFail
672
              , 'prop_Text_NodeLSIdempotent
673
              ]
674

    
675
-- ** Node tests
676

    
677
prop_Node_setAlias node name =
678
    Node.name newnode == Node.name node &&
679
    Node.alias newnode == name
680
    where _types = (node::Node.Node, name::String)
681
          newnode = Node.setAlias node name
682

    
683
prop_Node_setOffline node status =
684
    Node.offline newnode ==? status
685
    where newnode = Node.setOffline node status
686

    
687
prop_Node_setXmem node xm =
688
    Node.xMem newnode ==? xm
689
    where newnode = Node.setXmem node xm
690

    
691
prop_Node_setMcpu node mc =
692
    Node.mCpu newnode ==? mc
693
    where newnode = Node.setMcpu node mc
694

    
695
-- | Check that an instance add with too high memory or disk will be
696
-- rejected.
697
prop_Node_addPriFM node inst = Instance.mem inst >= Node.fMem node &&
698
                               not (Node.failN1 node) &&
699
                               not (Instance.instanceOffline inst)
700
                               ==>
701
                               case Node.addPri node inst'' of
702
                                 Types.OpFail Types.FailMem -> True
703
                                 _ -> False
704
    where _types = (node::Node.Node, inst::Instance.Instance)
705
          inst' = setInstanceSmallerThanNode node inst
706
          inst'' = inst' { Instance.mem = Instance.mem inst }
707

    
708
prop_Node_addPriFD node inst = Instance.dsk inst >= Node.fDsk node &&
709
                               not (Node.failN1 node)
710
                               ==>
711
                               case Node.addPri node inst'' of
712
                                 Types.OpFail Types.FailDisk -> True
713
                                 _ -> False
714
    where _types = (node::Node.Node, inst::Instance.Instance)
715
          inst' = setInstanceSmallerThanNode node inst
716
          inst'' = inst' { Instance.dsk = Instance.dsk inst }
717

    
718
prop_Node_addPriFC node inst (Positive extra) =
719
    not (Node.failN1 node) &&
720
    not (Instance.instanceOffline inst) ==>
721
        case Node.addPri node inst'' of
722
          Types.OpFail Types.FailCPU -> True
723
          _ -> False
724
    where _types = (node::Node.Node, inst::Instance.Instance)
725
          inst' = setInstanceSmallerThanNode node inst
726
          inst'' = inst' { Instance.vcpus = Node.availCpu node + extra }
727

    
728
-- | Check that an instance add with too high memory or disk will be
729
-- rejected.
730
prop_Node_addSec node inst pdx =
731
    ((Instance.mem inst >= (Node.fMem node - Node.rMem node) &&
732
      not (Instance.instanceOffline inst)) ||
733
     Instance.dsk inst >= Node.fDsk node) &&
734
    not (Node.failN1 node)
735
    ==> isFailure (Node.addSec node inst pdx)
736
        where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
737

    
738
-- | Check that an offline instance with reasonable disk size can always
739
-- be added.
740
prop_Node_addPriOffline node =
741
    forAll (arbitrary `suchThat`
742
            (\ x ->  (Instance.dsk x  < Node.fDsk node) &&
743
                      Instance.instanceOffline x)) $ \inst ->
744
    case Node.addPri node inst of
745
      Types.OpGood _ -> True
746
      _ -> False
747

    
748
prop_Node_addSecOffline node pdx =
749
    forAll (arbitrary `suchThat`
750
            (\ x ->  (Instance.dsk x  < Node.fDsk node) &&
751
                      Instance.instanceOffline x)) $ \inst ->
752
    case Node.addSec node inst pdx of
753
      Types.OpGood _ -> True
754
      _ -> False
755

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

    
789
-- | Check mdsk setting.
790
prop_Node_setMdsk node mx =
791
    Node.loDsk node' >= 0 &&
792
    fromIntegral (Node.loDsk node') <= Node.tDsk node &&
793
    Node.availDisk node' >= 0 &&
794
    Node.availDisk node' <= Node.fDsk node' &&
795
    fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
796
    Node.mDsk node' == mx'
797
    where _types = (node::Node.Node, mx::SmallRatio)
798
          node' = Node.setMdsk node mx'
799
          SmallRatio mx' = mx
800

    
801
-- Check tag maps
802
prop_Node_tagMaps_idempotent tags =
803
    Node.delTags (Node.addTags m tags) tags ==? m
804
    where m = Data.Map.empty
805

    
806
prop_Node_tagMaps_reject tags =
807
    not (null tags) ==>
808
    all (\t -> Node.rejectAddTags m [t]) tags
809
    where m = Node.addTags Data.Map.empty tags
810

    
811
prop_Node_showField node =
812
  forAll (elements Node.defaultFields) $ \ field ->
813
  fst (Node.showHeader field) /= Types.unknownField &&
814
  Node.showField node field /= Types.unknownField
815

    
816
prop_Node_computeGroups nodes =
817
  let ng = Node.computeGroups nodes
818
      onlyuuid = map fst ng
819
  in length nodes == sum (map (length . snd) ng) &&
820
     all (\(guuid, ns) -> all ((== guuid) . Node.group) ns) ng &&
821
     length (nub onlyuuid) == length onlyuuid &&
822
     (null nodes || not (null ng))
823

    
824
testSuite "Node"
825
              [ 'prop_Node_setAlias
826
              , 'prop_Node_setOffline
827
              , 'prop_Node_setMcpu
828
              , 'prop_Node_setXmem
829
              , 'prop_Node_addPriFM
830
              , 'prop_Node_addPriFD
831
              , 'prop_Node_addPriFC
832
              , 'prop_Node_addSec
833
              , 'prop_Node_addPriOffline
834
              , 'prop_Node_addSecOffline
835
              , 'prop_Node_rMem
836
              , 'prop_Node_setMdsk
837
              , 'prop_Node_tagMaps_idempotent
838
              , 'prop_Node_tagMaps_reject
839
              , 'prop_Node_showField
840
              , 'prop_Node_computeGroups
841
              ]
842

    
843
-- ** Cluster tests
844

    
845
-- | Check that the cluster score is close to zero for a homogeneous
846
-- cluster.
847
prop_Score_Zero node =
848
    forAll (choose (1, 1024)) $ \count ->
849
    (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
850
     (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
851
    let fn = Node.buildPeers node Container.empty
852
        nlst = replicate count fn
853
        score = Cluster.compCVNodes nlst
854
    -- we can't say == 0 here as the floating point errors accumulate;
855
    -- this should be much lower than the default score in CLI.hs
856
    in score <= 1e-12
857

    
858
-- | Check that cluster stats are sane.
859
prop_CStats_sane node =
860
    forAll (choose (1, 1024)) $ \count ->
861
    (not (Node.offline node) && not (Node.failN1 node) &&
862
     (Node.availDisk node > 0) && (Node.availMem node > 0)) ==>
863
    let fn = Node.buildPeers node Container.empty
864
        nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
865
        nl = Container.fromList nlst
866
        cstats = Cluster.totalResources nl
867
    in Cluster.csAdsk cstats >= 0 &&
868
       Cluster.csAdsk cstats <= Cluster.csFdsk cstats
869

    
870
-- | Check that one instance is allocated correctly, without
871
-- rebalances needed.
872
prop_ClusterAlloc_sane node inst =
873
    forAll (choose (5, 20)) $ \count ->
874
    not (Node.offline node)
875
            && not (Node.failN1 node)
876
            && Node.availDisk node > 0
877
            && Node.availMem node > 0
878
            ==>
879
    let nl = makeSmallCluster node count
880
        il = Container.empty
881
        inst' = setInstanceSmallerThanNode node inst
882
    in case Cluster.genAllocNodes defGroupList nl 2 True >>=
883
       Cluster.tryAlloc nl il inst' of
884
         Types.Bad _ -> False
885
         Types.Ok as ->
886
             case Cluster.asSolution as of
887
               Nothing -> False
888
               Just (xnl, xi, _, cv) ->
889
                   let il' = Container.add (Instance.idx xi) xi il
890
                       tbl = Cluster.Table xnl il' cv []
891
                   in not (canBalance tbl True True False)
892

    
893
-- | Checks that on a 2-5 node cluster, we can allocate a random
894
-- instance spec via tiered allocation (whatever the original instance
895
-- spec), on either one or two nodes.
896
prop_ClusterCanTieredAlloc node inst =
897
    forAll (choose (2, 5)) $ \count ->
898
    forAll (choose (1, 2)) $ \rqnodes ->
899
    not (Node.offline node)
900
            && not (Node.failN1 node)
901
            && isNodeBig node 4
902
            ==>
903
    let nl = makeSmallCluster node count
904
        il = Container.empty
905
        allocnodes = Cluster.genAllocNodes defGroupList nl rqnodes True
906
    in case allocnodes >>= \allocnodes' ->
907
        Cluster.tieredAlloc nl il (Just 1) inst allocnodes' [] [] of
908
         Types.Bad _ -> False
909
         Types.Ok (_, _, il', ixes, cstats) -> not (null ixes) &&
910
                                      IntMap.size il' == length ixes &&
911
                                      length ixes == length cstats
912

    
913
-- | Checks that on a 4-8 node cluster, once we allocate an instance,
914
-- we can also evacuate it.
915
prop_ClusterAllocEvac node inst =
916
    forAll (choose (4, 8)) $ \count ->
917
    not (Node.offline node)
918
            && not (Node.failN1 node)
919
            && isNodeBig node 4
920
            ==>
921
    let nl = makeSmallCluster node count
922
        il = Container.empty
923
        inst' = setInstanceSmallerThanNode node inst
924
    in case Cluster.genAllocNodes defGroupList nl 2 True >>=
925
       Cluster.tryAlloc nl il inst' of
926
         Types.Bad _ -> False
927
         Types.Ok as ->
928
             case Cluster.asSolution as of
929
               Nothing -> False
930
               Just (xnl, xi, _, _) ->
931
                   let sdx = Instance.sNode xi
932
                       il' = Container.add (Instance.idx xi) xi il
933
                   in case IAlloc.processRelocate defGroupList xnl il'
934
                          (Instance.idx xi) 1 [sdx] of
935
                        Types.Ok _ -> True
936
                        _ -> False
937

    
938
-- | Check that allocating multiple instances on a cluster, then
939
-- adding an empty node, results in a valid rebalance.
940
prop_ClusterAllocBalance =
941
    forAll (genNode (Just 5) (Just 128)) $ \node ->
942
    forAll (choose (3, 5)) $ \count ->
943
    not (Node.offline node) && not (Node.failN1 node) ==>
944
    let nl = makeSmallCluster node count
945
        (hnode, nl') = IntMap.deleteFindMax nl
946
        il = Container.empty
947
        allocnodes = Cluster.genAllocNodes defGroupList nl' 2 True
948
        i_templ = createInstance Types.unitMem Types.unitDsk Types.unitCpu
949
    in case allocnodes >>= \allocnodes' ->
950
        Cluster.iterateAlloc nl' il (Just 5) i_templ allocnodes' [] [] of
951
         Types.Bad _ -> False
952
         Types.Ok (_, xnl, il', _, _) ->
953
                   let ynl = Container.add (Node.idx hnode) hnode xnl
954
                       cv = Cluster.compCV ynl
955
                       tbl = Cluster.Table ynl il' cv []
956
                   in canBalance tbl True True False
957

    
958
-- | Checks consistency.
959
prop_ClusterCheckConsistency node inst =
960
  let nl = makeSmallCluster node 3
961
      [node1, node2, node3] = Container.elems nl
962
      node3' = node3 { Node.group = 1 }
963
      nl' = Container.add (Node.idx node3') node3' nl
964
      inst1 = Instance.setBoth inst (Node.idx node1) (Node.idx node2)
965
      inst2 = Instance.setBoth inst (Node.idx node1) Node.noSecondary
966
      inst3 = Instance.setBoth inst (Node.idx node1) (Node.idx node3)
967
      ccheck = Cluster.findSplitInstances nl' . Container.fromList
968
  in null (ccheck [(0, inst1)]) &&
969
     null (ccheck [(0, inst2)]) &&
970
     (not . null $ ccheck [(0, inst3)])
971

    
972
-- | For now, we only test that we don't lose instances during the split.
973
prop_ClusterSplitCluster node inst =
974
  forAll (choose (0, 100)) $ \icnt ->
975
  let nl = makeSmallCluster node 2
976
      (nl', il') = foldl (\(ns, is) _ -> assignInstance ns is inst 0 1)
977
                   (nl, Container.empty) [1..icnt]
978
      gni = Cluster.splitCluster nl' il'
979
  in sum (map (Container.size . snd . snd) gni) == icnt &&
980
     all (\(guuid, (nl'', _)) -> all ((== guuid) . Node.group)
981
                                 (Container.elems nl'')) gni
982

    
983
testSuite "Cluster"
984
              [ 'prop_Score_Zero
985
              , 'prop_CStats_sane
986
              , 'prop_ClusterAlloc_sane
987
              , 'prop_ClusterCanTieredAlloc
988
              , 'prop_ClusterAllocEvac
989
              , 'prop_ClusterAllocBalance
990
              , 'prop_ClusterCheckConsistency
991
              , 'prop_ClusterSplitCluster
992
              ]
993

    
994
-- ** OpCodes tests
995

    
996
-- | Check that opcode serialization is idempotent.
997
prop_OpCodes_serialization op =
998
  case J.readJSON (J.showJSON op) of
999
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
1000
    J.Ok op' -> op ==? op'
1001
  where _types = op::OpCodes.OpCode
1002

    
1003
testSuite "OpCodes"
1004
              [ 'prop_OpCodes_serialization ]
1005

    
1006
-- ** Jobs tests
1007

    
1008
-- | Check that (queued) job\/opcode status serialization is idempotent.
1009
prop_OpStatus_serialization os =
1010
  case J.readJSON (J.showJSON os) of
1011
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
1012
    J.Ok os' -> os ==? os'
1013
  where _types = os::Jobs.OpStatus
1014

    
1015
prop_JobStatus_serialization js =
1016
  case J.readJSON (J.showJSON js) of
1017
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
1018
    J.Ok js' -> js ==? js'
1019
  where _types = js::Jobs.JobStatus
1020

    
1021
testSuite "Jobs"
1022
              [ 'prop_OpStatus_serialization
1023
              , 'prop_JobStatus_serialization
1024
              ]
1025

    
1026
-- ** Loader tests
1027

    
1028
prop_Loader_lookupNode ktn inst node =
1029
  Loader.lookupNode nl inst node ==? Data.Map.lookup node nl
1030
  where nl = Data.Map.fromList ktn
1031

    
1032
prop_Loader_lookupInstance kti inst =
1033
  Loader.lookupInstance il inst ==? Data.Map.lookup inst il
1034
  where il = Data.Map.fromList kti
1035

    
1036
prop_Loader_assignIndices nodes =
1037
  Data.Map.size nassoc == length nodes &&
1038
  Container.size kt == length nodes &&
1039
  (if not (null nodes)
1040
   then maximum (IntMap.keys kt) == length nodes - 1
1041
   else True)
1042
  where (nassoc, kt) = Loader.assignIndices (map (\n -> (Node.name n, n)) nodes)
1043

    
1044
-- | Checks that the number of primary instances recorded on the nodes
1045
-- is zero.
1046
prop_Loader_mergeData ns =
1047
  let na = Container.fromList $ map (\n -> (Node.idx n, n)) ns
1048
  in case Loader.mergeData [] [] [] []
1049
         (Loader.emptyCluster {Loader.cdNodes = na}) of
1050
    Types.Bad _ -> False
1051
    Types.Ok (Loader.ClusterData _ nl il _) ->
1052
      let nodes = Container.elems nl
1053
          instances = Container.elems il
1054
      in (sum . map (length . Node.pList)) nodes == 0 &&
1055
         null instances
1056

    
1057
-- | Check that compareNameComponent on equal strings works.
1058
prop_Loader_compareNameComponent_equal :: String -> Bool
1059
prop_Loader_compareNameComponent_equal s =
1060
  Loader.compareNameComponent s s ==
1061
    Loader.LookupResult Loader.ExactMatch s
1062

    
1063
-- | Check that compareNameComponent on prefix strings works.
1064
prop_Loader_compareNameComponent_prefix :: NonEmptyList Char -> String -> Bool
1065
prop_Loader_compareNameComponent_prefix (NonEmpty s1) s2 =
1066
  Loader.compareNameComponent (s1 ++ "." ++ s2) s1 ==
1067
    Loader.LookupResult Loader.PartialMatch s1
1068

    
1069
testSuite "Loader"
1070
              [ 'prop_Loader_lookupNode
1071
              , 'prop_Loader_lookupInstance
1072
              , 'prop_Loader_assignIndices
1073
              , 'prop_Loader_mergeData
1074
              , 'prop_Loader_compareNameComponent_equal
1075
              , 'prop_Loader_compareNameComponent_prefix
1076
              ]
1077

    
1078
-- ** Types tests
1079

    
1080
prop_Types_AllocPolicy_serialisation apol =
1081
    case J.readJSON (J.showJSON apol) of
1082
      J.Ok p -> printTestCase ("invalid deserialisation " ++ show p) $
1083
                p == apol
1084
      J.Error s -> printTestCase ("failed to deserialise: " ++ s) False
1085
    where _types = apol::Types.AllocPolicy
1086

    
1087
prop_Types_DiskTemplate_serialisation dt =
1088
    case J.readJSON (J.showJSON dt) of
1089
      J.Ok p -> printTestCase ("invalid deserialisation " ++ show p) $
1090
                p == dt
1091
      J.Error s -> printTestCase ("failed to deserialise: " ++ s)
1092
                   False
1093
    where _types = dt::Types.DiskTemplate
1094

    
1095
prop_Types_opToResult op =
1096
    case op of
1097
      Types.OpFail _ -> Types.isBad r
1098
      Types.OpGood v -> case r of
1099
                          Types.Bad _ -> False
1100
                          Types.Ok v' -> v == v'
1101
    where r = Types.opToResult op
1102
          _types = op::Types.OpResult Int
1103

    
1104
prop_Types_eitherToResult ei =
1105
    case ei of
1106
      Left _ -> Types.isBad r
1107
      Right v -> case r of
1108
                   Types.Bad _ -> False
1109
                   Types.Ok v' -> v == v'
1110
    where r = Types.eitherToResult ei
1111
          _types = ei::Either String Int
1112

    
1113
testSuite "Types"
1114
              [ 'prop_Types_AllocPolicy_serialisation
1115
              , 'prop_Types_DiskTemplate_serialisation
1116
              , 'prop_Types_opToResult
1117
              , 'prop_Types_eitherToResult
1118
              ]