Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / QC.hs @ d6eec019

History | View | Annotate | Download (40.9 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2

    
3
{-| Unittests for ganeti-htools.
4

    
5
-}
6

    
7
{-
8

    
9
Copyright (C) 2009, 2010, 2011, 2012 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.JSON as JSON
59
import qualified Ganeti.HTools.Loader as Loader
60
import qualified Ganeti.HTools.Luxi
61
import qualified Ganeti.HTools.Node as Node
62
import qualified Ganeti.HTools.Group as Group
63
import qualified Ganeti.HTools.PeerMap as PeerMap
64
import qualified Ganeti.HTools.Rapi
65
import qualified Ganeti.HTools.Simu
66
import qualified Ganeti.HTools.Text as Text
67
import qualified Ganeti.HTools.Types as Types
68
import qualified Ganeti.HTools.Utils as Utils
69
import qualified Ganeti.HTools.Version
70
import qualified Ganeti.Constants as C
71

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

    
77
import Ganeti.HTools.QCHelper (testSuite)
78

    
79
-- * Constants
80

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

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

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

    
93
-- | Null iPolicy, and by null we mean very liberal.
94
nullIPolicy = Types.IPolicy
95
  { Types.iPolicyMinSpec = Types.ISpec { Types.iSpecMemorySize = 0
96
                                       , Types.iSpecCpuCount   = 0
97
                                       , Types.iSpecDiskSize   = 0
98
                                       , Types.iSpecDiskCount  = 0
99
                                       , Types.iSpecNicCount   = 0
100
                                       }
101
  , Types.iPolicyMaxSpec = Types.ISpec { Types.iSpecMemorySize = maxBound
102
                                       , Types.iSpecCpuCount   = maxBound
103
                                       , Types.iSpecDiskSize   = maxBound
104
                                       , Types.iSpecDiskCount  = C.maxDisks
105
                                       , Types.iSpecNicCount   = C.maxNics
106
                                       }
107
  , Types.iPolicyStdSpec = Types.ISpec { Types.iSpecMemorySize = Types.unitMem
108
                                       , Types.iSpecCpuCount   = Types.unitCpu
109
                                       , Types.iSpecDiskSize   = Types.unitDsk
110
                                       , Types.iSpecDiskCount  = 1
111
                                       , Types.iSpecNicCount   = 1
112
                                       }
113
  , Types.iPolicyDiskTemplates = [Types.DTDrbd8, Types.DTPlain]
114
  }
115

    
116

    
117
defGroup :: Group.Group
118
defGroup = flip Group.setIdx 0 $
119
             Group.create "default" Types.defaultGroupID Types.AllocPreferred
120
                  nullIPolicy
121

    
122
defGroupList :: Group.List
123
defGroupList = Container.fromList [(Group.idx defGroup, defGroup)]
124

    
125
defGroupAssoc :: Data.Map.Map String Types.Gdx
126
defGroupAssoc = Data.Map.singleton (Group.uuid defGroup) (Group.idx defGroup)
127

    
128
-- * Helper functions
129

    
130
-- | Simple checker for whether OpResult is fail or pass.
131
isFailure :: Types.OpResult a -> Bool
132
isFailure (Types.OpFail _) = True
133
isFailure _ = False
134

    
135
-- | Checks for equality with proper annotation.
136
(==?) :: (Show a, Eq a) => a -> a -> Property
137
(==?) x y = printTestCase
138
            ("Expected equality, but '" ++
139
             show x ++ "' /= '" ++ show y ++ "'") (x == y)
140
infix 3 ==?
141

    
142
-- | Update an instance to be smaller than a node.
143
setInstanceSmallerThanNode node inst =
144
  inst { Instance.mem = Node.availMem node `div` 2
145
       , Instance.dsk = Node.availDisk node `div` 2
146
       , Instance.vcpus = Node.availCpu node `div` 2
147
       }
148

    
149
-- | Create an instance given its spec.
150
createInstance mem dsk vcpus =
151
  Instance.create "inst-unnamed" mem dsk vcpus Types.Running [] True (-1) (-1)
152
    Types.DTDrbd8
153

    
154
-- | Create a small cluster by repeating a node spec.
155
makeSmallCluster :: Node.Node -> Int -> Node.List
156
makeSmallCluster node count =
157
  let fn = Node.buildPeers node Container.empty
158
      namelst = map (\n -> (Node.name n, n)) (replicate count fn)
159
      (_, nlst) = Loader.assignIndices namelst
160
  in nlst
161

    
162
-- | Make a small cluster, both nodes and instances.
163
makeSmallEmptyCluster :: Node.Node -> Int -> Instance.Instance
164
                      -> (Node.List, Instance.List, Instance.Instance)
165
makeSmallEmptyCluster node count inst =
166
  (makeSmallCluster node count, Container.empty,
167
   setInstanceSmallerThanNode node inst)
168

    
169
-- | Checks if a node is "big" enough.
170
isNodeBig :: Node.Node -> Int -> Bool
171
isNodeBig node size = Node.availDisk node > size * Types.unitDsk
172
                      && Node.availMem node > size * Types.unitMem
173
                      && Node.availCpu node > size * Types.unitCpu
174

    
175
canBalance :: Cluster.Table -> Bool -> Bool -> Bool -> Bool
176
canBalance tbl dm im evac = isJust $ Cluster.tryBalance tbl dm im evac 0 0
177

    
178
-- | Assigns a new fresh instance to a cluster; this is not
179
-- allocation, so no resource checks are done.
180
assignInstance :: Node.List -> Instance.List -> Instance.Instance ->
181
                  Types.Idx -> Types.Idx ->
182
                  (Node.List, Instance.List)
183
assignInstance nl il inst pdx sdx =
184
  let pnode = Container.find pdx nl
185
      snode = Container.find sdx nl
186
      maxiidx = if Container.null il
187
                  then 0
188
                  else fst (Container.findMax il) + 1
189
      inst' = inst { Instance.idx = maxiidx,
190
                     Instance.pNode = pdx, Instance.sNode = sdx }
191
      pnode' = Node.setPri pnode inst'
192
      snode' = Node.setSec snode inst'
193
      nl' = Container.addTwo pdx pnode' sdx snode' nl
194
      il' = Container.add maxiidx inst' il
195
  in (nl', il')
196

    
197
-- * Arbitrary instances
198

    
199
-- | Defines a DNS name.
200
newtype DNSChar = DNSChar { dnsGetChar::Char }
201

    
202
instance Arbitrary DNSChar where
203
  arbitrary = do
204
    x <- elements (['a'..'z'] ++ ['0'..'9'] ++ "_-")
205
    return (DNSChar x)
206

    
207
getName :: Gen String
208
getName = do
209
  n <- choose (1, 64)
210
  dn <- vector n::Gen [DNSChar]
211
  return (map dnsGetChar dn)
212

    
213
getFQDN :: Gen String
214
getFQDN = do
215
  felem <- getName
216
  ncomps <- choose (1, 4)
217
  frest <- vector ncomps::Gen [[DNSChar]]
218
  let frest' = map (map dnsGetChar) frest
219
  return (felem ++ "." ++ intercalate "." frest')
220

    
221
instance Arbitrary Types.InstanceStatus where
222
    arbitrary = elements [minBound..maxBound]
223

    
224
-- let's generate a random instance
225
instance Arbitrary Instance.Instance where
226
  arbitrary = do
227
    name <- getFQDN
228
    mem <- choose (0, maxMem)
229
    dsk <- choose (0, maxDsk)
230
    run_st <- arbitrary
231
    pn <- arbitrary
232
    sn <- arbitrary
233
    vcpus <- choose (0, maxCpu)
234
    return $ Instance.create name mem dsk vcpus run_st [] True pn sn
235
              Types.DTDrbd8
236

    
237
-- | Generas an arbitrary node based on sizing information.
238
genNode :: Maybe Int -- ^ Minimum node size in terms of units
239
        -> Maybe Int -- ^ Maximum node size (when Nothing, bounded
240
                     -- just by the max... constants)
241
        -> Gen Node.Node
242
genNode min_multiplier max_multiplier = do
243
  let (base_mem, base_dsk, base_cpu) =
244
        case min_multiplier of
245
          Just mm -> (mm * Types.unitMem,
246
                      mm * Types.unitDsk,
247
                      mm * Types.unitCpu)
248
          Nothing -> (0, 0, 0)
249
      (top_mem, top_dsk, top_cpu)  =
250
        case max_multiplier of
251
          Just mm -> (mm * Types.unitMem,
252
                      mm * Types.unitDsk,
253
                      mm * Types.unitCpu)
254
          Nothing -> (maxMem, maxDsk, maxCpu)
255
  name  <- getFQDN
256
  mem_t <- choose (base_mem, top_mem)
257
  mem_f <- choose (base_mem, mem_t)
258
  mem_n <- choose (0, mem_t - mem_f)
259
  dsk_t <- choose (base_dsk, top_dsk)
260
  dsk_f <- choose (base_dsk, dsk_t)
261
  cpu_t <- choose (base_cpu, top_cpu)
262
  offl  <- arbitrary
263
  let n = Node.create name (fromIntegral mem_t) mem_n mem_f
264
          (fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) offl 0
265
      n' = Node.setPolicy nullIPolicy n
266
  return $ Node.buildPeers n' Container.empty
267

    
268
-- and a random node
269
instance Arbitrary Node.Node where
270
  arbitrary = genNode Nothing Nothing
271

    
272
-- replace disks
273
instance Arbitrary OpCodes.ReplaceDisksMode where
274
  arbitrary = elements [minBound..maxBound]
275

    
276
instance Arbitrary OpCodes.OpCode where
277
  arbitrary = do
278
    op_id <- elements [ "OP_TEST_DELAY"
279
                      , "OP_INSTANCE_REPLACE_DISKS"
280
                      , "OP_INSTANCE_FAILOVER"
281
                      , "OP_INSTANCE_MIGRATE"
282
                      ]
283
    case op_id of
284
      "OP_TEST_DELAY" ->
285
        liftM3 OpCodes.OpTestDelay arbitrary arbitrary arbitrary
286
      "OP_INSTANCE_REPLACE_DISKS" ->
287
        liftM5 OpCodes.OpInstanceReplaceDisks arbitrary arbitrary
288
          arbitrary arbitrary arbitrary
289
      "OP_INSTANCE_FAILOVER" ->
290
        liftM3 OpCodes.OpInstanceFailover arbitrary arbitrary
291
          arbitrary
292
      "OP_INSTANCE_MIGRATE" ->
293
        liftM5 OpCodes.OpInstanceMigrate arbitrary arbitrary
294
          arbitrary arbitrary arbitrary
295
      _ -> fail "Wrong opcode"
296

    
297
instance Arbitrary Jobs.OpStatus where
298
  arbitrary = elements [minBound..maxBound]
299

    
300
instance Arbitrary Jobs.JobStatus where
301
  arbitrary = elements [minBound..maxBound]
302

    
303
newtype SmallRatio = SmallRatio Double deriving Show
304
instance Arbitrary SmallRatio where
305
  arbitrary = do
306
    v <- choose (0, 1)
307
    return $ SmallRatio v
308

    
309
instance Arbitrary Types.AllocPolicy where
310
  arbitrary = elements [minBound..maxBound]
311

    
312
instance Arbitrary Types.DiskTemplate where
313
  arbitrary = elements [minBound..maxBound]
314

    
315
instance Arbitrary Types.FailMode where
316
  arbitrary = elements [minBound..maxBound]
317

    
318
instance Arbitrary a => Arbitrary (Types.OpResult a) where
319
  arbitrary = arbitrary >>= \c ->
320
              if c
321
                then liftM Types.OpGood arbitrary
322
                else liftM Types.OpFail arbitrary
323

    
324
-- * Actual tests
325

    
326
-- ** Utils tests
327

    
328
-- | If the list is not just an empty element, and if the elements do
329
-- not contain commas, then join+split should be idempotent.
330
prop_Utils_commaJoinSplit =
331
  forAll (arbitrary `suchThat`
332
          (\l -> l /= [""] && all (notElem ',') l )) $ \lst ->
333
  Utils.sepSplit ',' (Utils.commaJoin lst) ==? lst
334

    
335
-- | Split and join should always be idempotent.
336
prop_Utils_commaSplitJoin s =
337
  Utils.commaJoin (Utils.sepSplit ',' s) ==? s
338

    
339
-- | fromObjWithDefault, we test using the Maybe monad and an integer
340
-- value.
341
prop_Utils_fromObjWithDefault def_value random_key =
342
  -- a missing key will be returned with the default
343
  JSON.fromObjWithDefault [] random_key def_value == Just def_value &&
344
  -- a found key will be returned as is, not with default
345
  JSON.fromObjWithDefault [(random_key, J.showJSON def_value)]
346
       random_key (def_value+1) == Just def_value
347
    where _types = def_value :: Integer
348

    
349
-- | Test that functional if' behaves like the syntactic sugar if.
350
prop_Utils_if'if :: Bool -> Int -> Int -> Gen Prop
351
prop_Utils_if'if cnd a b =
352
  Utils.if' cnd a b ==? if cnd then a else b
353

    
354
-- | Test basic select functionality
355
prop_Utils_select :: Int      -- ^ Default result
356
                  -> [Int]    -- ^ List of False values
357
                  -> [Int]    -- ^ List of True values
358
                  -> Gen Prop -- ^ Test result
359
prop_Utils_select def lst1 lst2 =
360
  Utils.select def (flist ++ tlist) ==? expectedresult
361
    where expectedresult = Utils.if' (null lst2) def (head lst2)
362
          flist = zip (repeat False) lst1
363
          tlist = zip (repeat True)  lst2
364

    
365
-- | Test basic select functionality with undefined default
366
prop_Utils_select_undefd :: [Int]            -- ^ List of False values
367
                         -> NonEmptyList Int -- ^ List of True values
368
                         -> Gen Prop         -- ^ Test result
369
prop_Utils_select_undefd lst1 (NonEmpty lst2) =
370
  Utils.select undefined (flist ++ tlist) ==? head lst2
371
    where flist = zip (repeat False) lst1
372
          tlist = zip (repeat True)  lst2
373

    
374
-- | Test basic select functionality with undefined list values
375
prop_Utils_select_undefv :: [Int]            -- ^ List of False values
376
                         -> NonEmptyList Int -- ^ List of True values
377
                         -> Gen Prop         -- ^ Test result
378
prop_Utils_select_undefv lst1 (NonEmpty lst2) =
379
  Utils.select undefined cndlist ==? head lst2
380
    where flist = zip (repeat False) lst1
381
          tlist = zip (repeat True)  lst2
382
          cndlist = flist ++ tlist ++ [undefined]
383

    
384
prop_Utils_parseUnit (NonNegative n) =
385
  Utils.parseUnit (show n) == Types.Ok n &&
386
  Utils.parseUnit (show n ++ "m") == Types.Ok n &&
387
  (case Utils.parseUnit (show n ++ "M") of
388
     Types.Ok m -> if n > 0
389
                     then m < n  -- for positive values, X MB is < than X MiB
390
                     else m == 0 -- but for 0, 0 MB == 0 MiB
391
     Types.Bad _ -> False) &&
392
  Utils.parseUnit (show n ++ "g") == Types.Ok (n*1024) &&
393
  Utils.parseUnit (show n ++ "t") == Types.Ok (n*1048576) &&
394
  Types.isBad (Utils.parseUnit (show n ++ "x")::Types.Result Int)
395
    where _types = n::Int
396

    
397
-- | Test list for the Utils module.
398
testSuite "Utils"
399
            [ 'prop_Utils_commaJoinSplit
400
            , 'prop_Utils_commaSplitJoin
401
            , 'prop_Utils_fromObjWithDefault
402
            , 'prop_Utils_if'if
403
            , 'prop_Utils_select
404
            , 'prop_Utils_select_undefd
405
            , 'prop_Utils_select_undefv
406
            , 'prop_Utils_parseUnit
407
            ]
408

    
409
-- ** PeerMap tests
410

    
411
-- | Make sure add is idempotent.
412
prop_PeerMap_addIdempotent pmap key em =
413
  fn puniq ==? fn (fn puniq)
414
    where _types = (pmap::PeerMap.PeerMap,
415
                    key::PeerMap.Key, em::PeerMap.Elem)
416
          fn = PeerMap.add key em
417
          puniq = PeerMap.accumArray const pmap
418

    
419
-- | Make sure remove is idempotent.
420
prop_PeerMap_removeIdempotent pmap key =
421
  fn puniq ==? fn (fn puniq)
422
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
423
          fn = PeerMap.remove key
424
          puniq = PeerMap.accumArray const pmap
425

    
426
-- | Make sure a missing item returns 0.
427
prop_PeerMap_findMissing pmap key =
428
  PeerMap.find key (PeerMap.remove key puniq) ==? 0
429
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
430
          puniq = PeerMap.accumArray const pmap
431

    
432
-- | Make sure an added item is found.
433
prop_PeerMap_addFind pmap key em =
434
  PeerMap.find key (PeerMap.add key em puniq) ==? em
435
    where _types = (pmap::PeerMap.PeerMap,
436
                    key::PeerMap.Key, em::PeerMap.Elem)
437
          puniq = PeerMap.accumArray const pmap
438

    
439
-- | Manual check that maxElem returns the maximum indeed, or 0 for null.
440
prop_PeerMap_maxElem pmap =
441
  PeerMap.maxElem puniq ==? if null puniq then 0
442
                              else (maximum . snd . unzip) puniq
443
    where _types = pmap::PeerMap.PeerMap
444
          puniq = PeerMap.accumArray const pmap
445

    
446
-- | List of tests for the PeerMap module.
447
testSuite "PeerMap"
448
            [ 'prop_PeerMap_addIdempotent
449
            , 'prop_PeerMap_removeIdempotent
450
            , 'prop_PeerMap_maxElem
451
            , 'prop_PeerMap_addFind
452
            , 'prop_PeerMap_findMissing
453
            ]
454

    
455
-- ** Container tests
456

    
457
-- we silence the following due to hlint bug fixed in later versions
458
{-# ANN prop_Container_addTwo "HLint: ignore Avoid lambda" #-}
459
prop_Container_addTwo cdata i1 i2 =
460
  fn i1 i2 cont == fn i2 i1 cont &&
461
  fn i1 i2 cont == fn i1 i2 (fn i1 i2 cont)
462
    where _types = (cdata::[Int],
463
                    i1::Int, i2::Int)
464
          cont = foldl (\c x -> Container.add x x c) Container.empty cdata
465
          fn x1 x2 = Container.addTwo x1 x1 x2 x2
466

    
467
prop_Container_nameOf node =
468
  let nl = makeSmallCluster node 1
469
      fnode = head (Container.elems nl)
470
  in Container.nameOf nl (Node.idx fnode) ==? Node.name fnode
471

    
472
-- | We test that in a cluster, given a random node, we can find it by
473
-- its name and alias, as long as all names and aliases are unique,
474
-- and that we fail to find a non-existing name.
475
prop_Container_findByName node othername =
476
  forAll (choose (1, 20)) $ \ cnt ->
477
  forAll (choose (0, cnt - 1)) $ \ fidx ->
478
  forAll (vector cnt) $ \ names ->
479
  (length . nub) (map fst names ++ map snd names) ==
480
  length names * 2 &&
481
  othername `notElem` (map fst names ++ map snd names) ==>
482
  let nl = makeSmallCluster node cnt
483
      nodes = Container.elems nl
484
      nodes' = map (\((name, alias), nn) -> (Node.idx nn,
485
                                             nn { Node.name = name,
486
                                                  Node.alias = alias }))
487
               $ zip names nodes
488
      nl' = Container.fromList nodes'
489
      target = snd (nodes' !! fidx)
490
  in Container.findByName nl' (Node.name target) == Just target &&
491
     Container.findByName nl' (Node.alias target) == Just target &&
492
     isNothing (Container.findByName nl' othername)
493

    
494
testSuite "Container"
495
            [ 'prop_Container_addTwo
496
            , 'prop_Container_nameOf
497
            , 'prop_Container_findByName
498
            ]
499

    
500
-- ** Instance tests
501

    
502
-- Simple instance tests, we only have setter/getters
503

    
504
prop_Instance_creat inst =
505
  Instance.name inst ==? Instance.alias inst
506

    
507
prop_Instance_setIdx inst idx =
508
  Instance.idx (Instance.setIdx inst idx) ==? idx
509
    where _types = (inst::Instance.Instance, idx::Types.Idx)
510

    
511
prop_Instance_setName inst name =
512
  Instance.name newinst == name &&
513
  Instance.alias newinst == name
514
    where _types = (inst::Instance.Instance, name::String)
515
          newinst = Instance.setName inst name
516

    
517
prop_Instance_setAlias inst name =
518
  Instance.name newinst == Instance.name inst &&
519
  Instance.alias newinst == name
520
    where _types = (inst::Instance.Instance, name::String)
521
          newinst = Instance.setAlias inst name
522

    
523
prop_Instance_setPri inst pdx =
524
  Instance.pNode (Instance.setPri inst pdx) ==? pdx
525
    where _types = (inst::Instance.Instance, pdx::Types.Ndx)
526

    
527
prop_Instance_setSec inst sdx =
528
  Instance.sNode (Instance.setSec inst sdx) ==? sdx
529
    where _types = (inst::Instance.Instance, sdx::Types.Ndx)
530

    
531
prop_Instance_setBoth inst pdx sdx =
532
  Instance.pNode si == pdx && Instance.sNode si == sdx
533
    where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
534
          si = Instance.setBoth inst pdx sdx
535

    
536
prop_Instance_shrinkMG inst =
537
  Instance.mem inst >= 2 * Types.unitMem ==>
538
    case Instance.shrinkByType inst Types.FailMem of
539
      Types.Ok inst' -> Instance.mem inst' == Instance.mem inst - Types.unitMem
540
      _ -> False
541

    
542
prop_Instance_shrinkMF inst =
543
  forAll (choose (0, 2 * Types.unitMem - 1)) $ \mem ->
544
    let inst' = inst { Instance.mem = mem}
545
    in Types.isBad $ Instance.shrinkByType inst' Types.FailMem
546

    
547
prop_Instance_shrinkCG inst =
548
  Instance.vcpus inst >= 2 * Types.unitCpu ==>
549
    case Instance.shrinkByType inst Types.FailCPU of
550
      Types.Ok inst' ->
551
        Instance.vcpus inst' == Instance.vcpus inst - Types.unitCpu
552
      _ -> False
553

    
554
prop_Instance_shrinkCF inst =
555
  forAll (choose (0, 2 * Types.unitCpu - 1)) $ \vcpus ->
556
    let inst' = inst { Instance.vcpus = vcpus }
557
    in Types.isBad $ Instance.shrinkByType inst' Types.FailCPU
558

    
559
prop_Instance_shrinkDG inst =
560
  Instance.dsk inst >= 2 * Types.unitDsk ==>
561
    case Instance.shrinkByType inst Types.FailDisk of
562
      Types.Ok inst' ->
563
        Instance.dsk inst' == Instance.dsk inst - Types.unitDsk
564
      _ -> False
565

    
566
prop_Instance_shrinkDF inst =
567
  forAll (choose (0, 2 * Types.unitDsk - 1)) $ \dsk ->
568
    let inst' = inst { Instance.dsk = dsk }
569
    in Types.isBad $ Instance.shrinkByType inst' Types.FailDisk
570

    
571
prop_Instance_setMovable inst m =
572
  Instance.movable inst' ==? m
573
    where inst' = Instance.setMovable inst m
574

    
575
testSuite "Instance"
576
            [ 'prop_Instance_creat
577
            , 'prop_Instance_setIdx
578
            , 'prop_Instance_setName
579
            , 'prop_Instance_setAlias
580
            , 'prop_Instance_setPri
581
            , 'prop_Instance_setSec
582
            , 'prop_Instance_setBoth
583
            , 'prop_Instance_shrinkMG
584
            , 'prop_Instance_shrinkMF
585
            , 'prop_Instance_shrinkCG
586
            , 'prop_Instance_shrinkCF
587
            , 'prop_Instance_shrinkDG
588
            , 'prop_Instance_shrinkDF
589
            , 'prop_Instance_setMovable
590
            ]
591

    
592
-- ** Text backend tests
593

    
594
-- Instance text loader tests
595

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

    
636
prop_Text_Load_InstanceFail ktn fields =
637
  length fields /= 10 ==>
638
    case Text.loadInst nl fields of
639
      Types.Ok _ -> printTestCase "Managed to load instance from invalid\
640
                                  \ data" False
641
      Types.Bad msg -> printTestCase ("Unrecognised error message: " ++ msg) $
642
                       "Invalid/incomplete instance data: '" `isPrefixOf` msg
643
    where nl = Data.Map.fromList ktn
644

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

    
675
prop_Text_Load_NodeFail fields =
676
  length fields /= 8 ==> isNothing $ Text.loadNode Data.Map.empty fields
677

    
678
prop_Text_NodeLSIdempotent node =
679
  (Text.loadNode defGroupAssoc.
680
       Utils.sepSplit '|' . Text.serializeNode defGroupList) n ==
681
  Just (Node.name n, n)
682
    -- override failN1 to what loadNode returns by default
683
    where n = node { Node.failN1 = True, Node.offline = False
684
                   , Node.iPolicy = Types.defIPolicy }
685

    
686
testSuite "Text"
687
            [ 'prop_Text_Load_Instance
688
            , 'prop_Text_Load_InstanceFail
689
            , 'prop_Text_Load_Node
690
            , 'prop_Text_Load_NodeFail
691
            , 'prop_Text_NodeLSIdempotent
692
            ]
693

    
694
-- ** Node tests
695

    
696
prop_Node_setAlias node name =
697
  Node.name newnode == Node.name node &&
698
  Node.alias newnode == name
699
    where _types = (node::Node.Node, name::String)
700
          newnode = Node.setAlias node name
701

    
702
prop_Node_setOffline node status =
703
  Node.offline newnode ==? status
704
    where newnode = Node.setOffline node status
705

    
706
prop_Node_setXmem node xm =
707
  Node.xMem newnode ==? xm
708
    where newnode = Node.setXmem node xm
709

    
710
prop_Node_setMcpu node mc =
711
  Node.mCpu newnode ==? mc
712
    where newnode = Node.setMcpu node mc
713

    
714
-- | Check that an instance add with too high memory or disk will be
715
-- rejected.
716
prop_Node_addPriFM node inst =
717
  Instance.mem inst >= Node.fMem node && not (Node.failN1 node) &&
718
  not (Instance.instanceOffline inst) ==>
719
  case Node.addPri node inst'' of
720
    Types.OpFail Types.FailMem -> True
721
    _ -> False
722
  where _types = (node::Node.Node, inst::Instance.Instance)
723
        inst' = setInstanceSmallerThanNode node inst
724
        inst'' = inst' { Instance.mem = Instance.mem inst }
725

    
726
prop_Node_addPriFD node inst =
727
  Instance.dsk inst >= Node.fDsk node && not (Node.failN1 node) ==>
728
    case Node.addPri node inst'' of
729
      Types.OpFail Types.FailDisk -> True
730
      _ -> False
731
    where _types = (node::Node.Node, inst::Instance.Instance)
732
          inst' = setInstanceSmallerThanNode node inst
733
          inst'' = inst' { Instance.dsk = Instance.dsk inst }
734

    
735
prop_Node_addPriFC node inst (Positive extra) =
736
  not (Node.failN1 node) && not (Instance.instanceOffline inst) ==>
737
      case Node.addPri node inst'' of
738
        Types.OpFail Types.FailCPU -> True
739
        _ -> False
740
    where _types = (node::Node.Node, inst::Instance.Instance)
741
          inst' = setInstanceSmallerThanNode node inst
742
          inst'' = inst' { Instance.vcpus = Node.availCpu node + extra }
743

    
744
-- | Check that an instance add with too high memory or disk will be
745
-- rejected.
746
prop_Node_addSec node inst pdx =
747
  ((Instance.mem inst >= (Node.fMem node - Node.rMem node) &&
748
    not (Instance.instanceOffline inst)) ||
749
   Instance.dsk inst >= Node.fDsk node) &&
750
  not (Node.failN1 node) ==>
751
      isFailure (Node.addSec node inst pdx)
752
        where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
753

    
754
-- | Check that an offline instance with reasonable disk size can always
755
-- be added.
756
prop_Node_addPriOffline =
757
  forAll (arbitrary `suchThat` ((> 0) . Node.fMem)) $ \node ->
758
  forAll (arbitrary `suchThat`
759
          (\ x ->  (Instance.dsk x  < Node.fDsk node) &&
760
                   Instance.instanceOffline x)) $ \inst ->
761
  case Node.addPri node inst of
762
    Types.OpGood _ -> True
763
    _ -> False
764

    
765
prop_Node_addSecOffline pdx =
766
  forAll (arbitrary `suchThat` ((> 0) . Node.fMem)) $ \node ->
767
  forAll (arbitrary `suchThat`
768
          (\ x ->  (Instance.dsk x  < Node.fDsk node) &&
769
                   Instance.instanceOffline x)) $ \inst ->
770
  case Node.addSec node inst pdx of
771
    Types.OpGood _ -> True
772
    _ -> False
773

    
774
-- | Checks for memory reservation changes.
775
prop_Node_rMem inst =
776
  not (Instance.instanceOffline inst) ==>
777
  forAll (arbitrary `suchThat` ((> Types.unitMem) . Node.fMem)) $ \node ->
778
  -- ab = auto_balance, nb = non-auto_balance
779
  -- we use -1 as the primary node of the instance
780
  let inst' = inst { Instance.pNode = -1, Instance.autoBalance = True }
781
      inst_ab = setInstanceSmallerThanNode node inst'
782
      inst_nb = inst_ab { Instance.autoBalance = False }
783
      -- now we have the two instances, identical except the
784
      -- autoBalance attribute
785
      orig_rmem = Node.rMem node
786
      inst_idx = Instance.idx inst_ab
787
      node_add_ab = Node.addSec node inst_ab (-1)
788
      node_add_nb = Node.addSec node inst_nb (-1)
789
      node_del_ab = liftM (`Node.removeSec` inst_ab) node_add_ab
790
      node_del_nb = liftM (`Node.removeSec` inst_nb) node_add_nb
791
  in case (node_add_ab, node_add_nb, node_del_ab, node_del_nb) of
792
       (Types.OpGood a_ab, Types.OpGood a_nb,
793
        Types.OpGood d_ab, Types.OpGood d_nb) ->
794
         printTestCase "Consistency checks failed" $
795
           Node.rMem a_ab >  orig_rmem &&
796
           Node.rMem a_ab - orig_rmem == Instance.mem inst_ab &&
797
           Node.rMem a_nb == orig_rmem &&
798
           Node.rMem d_ab == orig_rmem &&
799
           Node.rMem d_nb == orig_rmem &&
800
           -- this is not related to rMem, but as good a place to
801
           -- test as any
802
           inst_idx `elem` Node.sList a_ab &&
803
           inst_idx `notElem` Node.sList d_ab
804
       x -> printTestCase ("Failed to add/remove instances: " ++ show x) False
805

    
806
-- | Check mdsk setting.
807
prop_Node_setMdsk node mx =
808
  Node.loDsk node' >= 0 &&
809
  fromIntegral (Node.loDsk node') <= Node.tDsk node &&
810
  Node.availDisk node' >= 0 &&
811
  Node.availDisk node' <= Node.fDsk node' &&
812
  fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
813
  Node.mDsk node' == mx'
814
    where _types = (node::Node.Node, mx::SmallRatio)
815
          node' = Node.setMdsk node mx'
816
          SmallRatio mx' = mx
817

    
818
-- Check tag maps
819
prop_Node_tagMaps_idempotent tags =
820
  Node.delTags (Node.addTags m tags) tags ==? m
821
    where m = Data.Map.empty
822

    
823
prop_Node_tagMaps_reject tags =
824
  not (null tags) ==>
825
  all (\t -> Node.rejectAddTags m [t]) tags
826
    where m = Node.addTags Data.Map.empty tags
827

    
828
prop_Node_showField node =
829
  forAll (elements Node.defaultFields) $ \ field ->
830
  fst (Node.showHeader field) /= Types.unknownField &&
831
  Node.showField node field /= Types.unknownField
832

    
833
prop_Node_computeGroups nodes =
834
  let ng = Node.computeGroups nodes
835
      onlyuuid = map fst ng
836
  in length nodes == sum (map (length . snd) ng) &&
837
     all (\(guuid, ns) -> all ((== guuid) . Node.group) ns) ng &&
838
     length (nub onlyuuid) == length onlyuuid &&
839
     (null nodes || not (null ng))
840

    
841
testSuite "Node"
842
            [ 'prop_Node_setAlias
843
            , 'prop_Node_setOffline
844
            , 'prop_Node_setMcpu
845
            , 'prop_Node_setXmem
846
            , 'prop_Node_addPriFM
847
            , 'prop_Node_addPriFD
848
            , 'prop_Node_addPriFC
849
            , 'prop_Node_addSec
850
            , 'prop_Node_addPriOffline
851
            , 'prop_Node_addSecOffline
852
            , 'prop_Node_rMem
853
            , 'prop_Node_setMdsk
854
            , 'prop_Node_tagMaps_idempotent
855
            , 'prop_Node_tagMaps_reject
856
            , 'prop_Node_showField
857
            , 'prop_Node_computeGroups
858
            ]
859

    
860
-- ** Cluster tests
861

    
862
-- | Check that the cluster score is close to zero for a homogeneous
863
-- cluster.
864
prop_Score_Zero node =
865
  forAll (choose (1, 1024)) $ \count ->
866
    (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
867
     (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
868
  let fn = Node.buildPeers node Container.empty
869
      nlst = replicate count fn
870
      score = Cluster.compCVNodes nlst
871
  -- we can't say == 0 here as the floating point errors accumulate;
872
  -- this should be much lower than the default score in CLI.hs
873
  in score <= 1e-12
874

    
875
-- | Check that cluster stats are sane.
876
prop_CStats_sane node =
877
  forAll (choose (1, 1024)) $ \count ->
878
    (not (Node.offline node) && not (Node.failN1 node) &&
879
     (Node.availDisk node > 0) && (Node.availMem node > 0)) ==>
880
  let fn = Node.buildPeers node Container.empty
881
      nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
882
      nl = Container.fromList nlst
883
      cstats = Cluster.totalResources nl
884
  in Cluster.csAdsk cstats >= 0 &&
885
     Cluster.csAdsk cstats <= Cluster.csFdsk cstats
886

    
887
-- | Check that one instance is allocated correctly, without
888
-- rebalances needed.
889
prop_ClusterAlloc_sane node inst =
890
  forAll (choose (5, 20)) $ \count ->
891
  not (Node.offline node)
892
        && not (Node.failN1 node)
893
        && Node.availDisk node > 0
894
        && Node.availMem node > 0
895
        ==>
896
  let (nl, il, inst') = makeSmallEmptyCluster node count inst
897
  in case Cluster.genAllocNodes defGroupList nl 2 True >>=
898
     Cluster.tryAlloc nl il inst' of
899
       Types.Bad _ -> False
900
       Types.Ok as ->
901
         case Cluster.asSolution as of
902
           Nothing -> False
903
           Just (xnl, xi, _, cv) ->
904
             let il' = Container.add (Instance.idx xi) xi il
905
                 tbl = Cluster.Table xnl il' cv []
906
             in not (canBalance tbl True True False)
907

    
908
-- | Checks that on a 2-5 node cluster, we can allocate a random
909
-- instance spec via tiered allocation (whatever the original instance
910
-- spec), on either one or two nodes.
911
prop_ClusterCanTieredAlloc node inst =
912
  forAll (choose (2, 5)) $ \count ->
913
  forAll (choose (1, 2)) $ \rqnodes ->
914
  not (Node.offline node)
915
        && not (Node.failN1 node)
916
        && isNodeBig node 4
917
        ==>
918
  let nl = makeSmallCluster node count
919
      il = Container.empty
920
      allocnodes = Cluster.genAllocNodes defGroupList nl rqnodes True
921
  in case allocnodes >>= \allocnodes' ->
922
    Cluster.tieredAlloc nl il (Just 1) inst allocnodes' [] [] of
923
       Types.Bad _ -> False
924
       Types.Ok (_, _, il', ixes, cstats) -> not (null ixes) &&
925
                                             IntMap.size il' == length ixes &&
926
                                             length ixes == length cstats
927

    
928
-- | Checks that on a 4-8 node cluster, once we allocate an instance,
929
-- we can also evacuate it.
930
prop_ClusterAllocEvac node inst =
931
  forAll (choose (4, 8)) $ \count ->
932
  not (Node.offline node)
933
        && not (Node.failN1 node)
934
        && isNodeBig node 4
935
        ==>
936
  let (nl, il, inst') = makeSmallEmptyCluster node count inst
937
  in case Cluster.genAllocNodes defGroupList nl 2 True >>=
938
     Cluster.tryAlloc nl il inst' of
939
       Types.Bad _ -> False
940
       Types.Ok as ->
941
         case Cluster.asSolution as of
942
           Nothing -> False
943
           Just (xnl, xi, _, _) ->
944
             let sdx = Instance.sNode xi
945
                 il' = Container.add (Instance.idx xi) xi il
946
             in case IAlloc.processRelocate defGroupList xnl il'
947
                  (Instance.idx xi) 1 [sdx] of
948
                  Types.Ok _ -> True
949
                  _ -> False
950

    
951
-- | Check that allocating multiple instances on a cluster, then
952
-- adding an empty node, results in a valid rebalance.
953
prop_ClusterAllocBalance =
954
  forAll (genNode (Just 5) (Just 128)) $ \node ->
955
  forAll (choose (3, 5)) $ \count ->
956
  not (Node.offline node) && not (Node.failN1 node) ==>
957
  let nl = makeSmallCluster node count
958
      (hnode, nl') = IntMap.deleteFindMax nl
959
      il = Container.empty
960
      allocnodes = Cluster.genAllocNodes defGroupList nl' 2 True
961
      i_templ = createInstance Types.unitMem Types.unitDsk Types.unitCpu
962
  in case allocnodes >>= \allocnodes' ->
963
    Cluster.iterateAlloc nl' il (Just 5) i_templ allocnodes' [] [] of
964
       Types.Bad _ -> printTestCase "Failed to allocate" False
965
       Types.Ok (_, _, _, [], _) -> printTestCase "Failed to allocate" False
966
       Types.Ok (_, xnl, il', _, _) ->
967
         let ynl = Container.add (Node.idx hnode) hnode xnl
968
             cv = Cluster.compCV ynl
969
             tbl = Cluster.Table ynl il' cv []
970
         in printTestCase "Failed to rebalance" $
971
            canBalance tbl True True False
972

    
973
-- | Checks consistency.
974
prop_ClusterCheckConsistency node inst =
975
  let nl = makeSmallCluster node 3
976
      [node1, node2, node3] = Container.elems nl
977
      node3' = node3 { Node.group = 1 }
978
      nl' = Container.add (Node.idx node3') node3' nl
979
      inst1 = Instance.setBoth inst (Node.idx node1) (Node.idx node2)
980
      inst2 = Instance.setBoth inst (Node.idx node1) Node.noSecondary
981
      inst3 = Instance.setBoth inst (Node.idx node1) (Node.idx node3)
982
      ccheck = Cluster.findSplitInstances nl' . Container.fromList
983
  in null (ccheck [(0, inst1)]) &&
984
     null (ccheck [(0, inst2)]) &&
985
     (not . null $ ccheck [(0, inst3)])
986

    
987
-- | For now, we only test that we don't lose instances during the split.
988
prop_ClusterSplitCluster node inst =
989
  forAll (choose (0, 100)) $ \icnt ->
990
  let nl = makeSmallCluster node 2
991
      (nl', il') = foldl (\(ns, is) _ -> assignInstance ns is inst 0 1)
992
                   (nl, Container.empty) [1..icnt]
993
      gni = Cluster.splitCluster nl' il'
994
  in sum (map (Container.size . snd . snd) gni) == icnt &&
995
     all (\(guuid, (nl'', _)) -> all ((== guuid) . Node.group)
996
                                 (Container.elems nl'')) gni
997

    
998
testSuite "Cluster"
999
            [ 'prop_Score_Zero
1000
            , 'prop_CStats_sane
1001
            , 'prop_ClusterAlloc_sane
1002
            , 'prop_ClusterCanTieredAlloc
1003
            , 'prop_ClusterAllocEvac
1004
            , 'prop_ClusterAllocBalance
1005
            , 'prop_ClusterCheckConsistency
1006
            , 'prop_ClusterSplitCluster
1007
            ]
1008

    
1009
-- ** OpCodes tests
1010

    
1011
-- | Check that opcode serialization is idempotent.
1012
prop_OpCodes_serialization op =
1013
  case J.readJSON (J.showJSON op) of
1014
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
1015
    J.Ok op' -> op ==? op'
1016
  where _types = op::OpCodes.OpCode
1017

    
1018
testSuite "OpCodes"
1019
            [ 'prop_OpCodes_serialization ]
1020

    
1021
-- ** Jobs tests
1022

    
1023
-- | Check that (queued) job\/opcode status serialization is idempotent.
1024
prop_OpStatus_serialization os =
1025
  case J.readJSON (J.showJSON os) of
1026
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
1027
    J.Ok os' -> os ==? os'
1028
  where _types = os::Jobs.OpStatus
1029

    
1030
prop_JobStatus_serialization js =
1031
  case J.readJSON (J.showJSON js) of
1032
    J.Error e -> printTestCase ("Cannot deserialise: " ++ e) False
1033
    J.Ok js' -> js ==? js'
1034
  where _types = js::Jobs.JobStatus
1035

    
1036
testSuite "Jobs"
1037
            [ 'prop_OpStatus_serialization
1038
            , 'prop_JobStatus_serialization
1039
            ]
1040

    
1041
-- ** Loader tests
1042

    
1043
prop_Loader_lookupNode ktn inst node =
1044
  Loader.lookupNode nl inst node ==? Data.Map.lookup node nl
1045
    where nl = Data.Map.fromList ktn
1046

    
1047
prop_Loader_lookupInstance kti inst =
1048
  Loader.lookupInstance il inst ==? Data.Map.lookup inst il
1049
    where il = Data.Map.fromList kti
1050

    
1051
prop_Loader_assignIndices nodes =
1052
  Data.Map.size nassoc == length nodes &&
1053
  Container.size kt == length nodes &&
1054
  (if not (null nodes)
1055
   then maximum (IntMap.keys kt) == length nodes - 1
1056
   else True)
1057
    where (nassoc, kt) =
1058
            Loader.assignIndices (map (\n -> (Node.name n, n)) nodes)
1059

    
1060
-- | Checks that the number of primary instances recorded on the nodes
1061
-- is zero.
1062
prop_Loader_mergeData ns =
1063
  let na = Container.fromList $ map (\n -> (Node.idx n, n)) ns
1064
  in case Loader.mergeData [] [] [] []
1065
         (Loader.emptyCluster {Loader.cdNodes = na}) of
1066
    Types.Bad _ -> False
1067
    Types.Ok (Loader.ClusterData _ nl il _ _) ->
1068
      let nodes = Container.elems nl
1069
          instances = Container.elems il
1070
      in (sum . map (length . Node.pList)) nodes == 0 &&
1071
         null instances
1072

    
1073
-- | Check that compareNameComponent on equal strings works.
1074
prop_Loader_compareNameComponent_equal :: String -> Bool
1075
prop_Loader_compareNameComponent_equal s =
1076
  Loader.compareNameComponent s s ==
1077
    Loader.LookupResult Loader.ExactMatch s
1078

    
1079
-- | Check that compareNameComponent on prefix strings works.
1080
prop_Loader_compareNameComponent_prefix :: NonEmptyList Char -> String -> Bool
1081
prop_Loader_compareNameComponent_prefix (NonEmpty s1) s2 =
1082
  Loader.compareNameComponent (s1 ++ "." ++ s2) s1 ==
1083
    Loader.LookupResult Loader.PartialMatch s1
1084

    
1085
testSuite "Loader"
1086
            [ 'prop_Loader_lookupNode
1087
            , 'prop_Loader_lookupInstance
1088
            , 'prop_Loader_assignIndices
1089
            , 'prop_Loader_mergeData
1090
            , 'prop_Loader_compareNameComponent_equal
1091
            , 'prop_Loader_compareNameComponent_prefix
1092
            ]
1093

    
1094
-- ** Types tests
1095

    
1096
prop_Types_AllocPolicy_serialisation apol =
1097
  case J.readJSON (J.showJSON apol) of
1098
    J.Ok p -> printTestCase ("invalid deserialisation " ++ show p) $
1099
              p == apol
1100
    J.Error s -> printTestCase ("failed to deserialise: " ++ s) False
1101
      where _types = apol::Types.AllocPolicy
1102

    
1103
prop_Types_DiskTemplate_serialisation dt =
1104
  case J.readJSON (J.showJSON dt) of
1105
    J.Ok p -> printTestCase ("invalid deserialisation " ++ show p) $
1106
              p == dt
1107
    J.Error s -> printTestCase ("failed to deserialise: " ++ s)
1108
                 False
1109
      where _types = dt::Types.DiskTemplate
1110

    
1111
prop_Types_opToResult op =
1112
  case op of
1113
    Types.OpFail _ -> Types.isBad r
1114
    Types.OpGood v -> case r of
1115
                        Types.Bad _ -> False
1116
                        Types.Ok v' -> v == v'
1117
  where r = Types.opToResult op
1118
        _types = op::Types.OpResult Int
1119

    
1120
prop_Types_eitherToResult ei =
1121
  case ei of
1122
    Left _ -> Types.isBad r
1123
    Right v -> case r of
1124
                 Types.Bad _ -> False
1125
                 Types.Ok v' -> v == v'
1126
    where r = Types.eitherToResult ei
1127
          _types = ei::Either String Int
1128

    
1129
testSuite "Types"
1130
            [ 'prop_Types_AllocPolicy_serialisation
1131
            , 'prop_Types_DiskTemplate_serialisation
1132
            , 'prop_Types_opToResult
1133
            , 'prop_Types_eitherToResult
1134
            ]