Revision fce98abd

b/Makefile.am
549 549
	  -osuf $$BINARY.o -hisuf $$BINARY.hi \
550 550
	  $(HEXTRA) $(HEXTRA_INT) $@
551 551
# for the htools/test binary, we need to enable profiling/coverage
552
htools/test: HEXTRA_INT=-fhpc -Wwarn -fno-warn-missing-signatures \
553
	-fno-warn-monomorphism-restriction -fno-warn-orphans \
554
	-fno-warn-missing-methods -fno-warn-unused-imports
552
htools/test: HEXTRA_INT=-fhpc
555 553

  
556 554
# we compile the hpc-htools binary with the program coverage
557 555
htools/hpc-htools: HEXTRA_INT=-fhpc
b/htools/Ganeti/HTools/QC.hs
1 1
{-# LANGUAGE TemplateHaskell #-}
2
{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-unused-imports #-}
3

  
4
-- FIXME: should remove the no-warn-unused-imports option, once we get
5
-- around to testing function from all modules; until then, we keep
6
-- the (unused) imports here to generate correct coverage (0 for
7
-- modules we don't use)
2 8

  
3 9
{-| Unittests for ganeti-htools.
4 10

  
......
46 52

  
47 53
import Test.QuickCheck
48 54
import Text.Printf (printf)
49
import Data.List (findIndex, intercalate, nub, isPrefixOf)
50
import qualified Data.Set as Set
55
import Data.List (intercalate, nub, isPrefixOf)
51 56
import Data.Maybe
52 57
import Control.Monad
53 58
import Control.Applicative
......
124 129
allDiskTemplates = [minBound..maxBound]
125 130

  
126 131
-- | Null iPolicy, and by null we mean very liberal.
132
nullIPolicy :: Types.IPolicy
127 133
nullIPolicy = Types.IPolicy
128 134
  { Types.iPolicyMinSpec = Types.ISpec { Types.iSpecMemorySize = 0
129 135
                                       , Types.iSpecCpuCount   = 0
......
183 189
failTest msg = printTestCase msg False
184 190

  
185 191
-- | Update an instance to be smaller than a node.
192
setInstanceSmallerThanNode :: Node.Node
193
                           -> Instance.Instance -> Instance.Instance
186 194
setInstanceSmallerThanNode node inst =
187 195
  inst { Instance.mem = Node.availMem node `div` 2
188 196
       , Instance.dsk = Node.availDisk node `div` 2
......
190 198
       }
191 199

  
192 200
-- | Create an instance given its spec.
201
createInstance :: Int -> Int -> Int -> Instance.Instance
193 202
createInstance mem dsk vcpus =
194 203
  Instance.create "inst-unnamed" mem dsk vcpus Types.Running [] True (-1) (-1)
195 204
    Types.DTDrbd8 1
......
512 521
-- ** Utils tests
513 522

  
514 523
-- | Helper to generate a small string that doesn't contain commas.
524
genNonCommaString :: Gen [Char]
515 525
genNonCommaString = do
516 526
  size <- choose (0, 20) -- arbitrary max size
517 527
  vectorOf size (arbitrary `suchThat` ((/=) ','))
518 528

  
519 529
-- | If the list is not just an empty element, and if the elements do
520 530
-- not contain commas, then join+split should be idempotent.
531
prop_Utils_commaJoinSplit :: Property
521 532
prop_Utils_commaJoinSplit =
522 533
  forAll (choose (0, 20)) $ \llen ->
523 534
  forAll (vectorOf llen genNonCommaString `suchThat` ((/=) [""])) $ \lst ->
524 535
  Utils.sepSplit ',' (Utils.commaJoin lst) ==? lst
525 536

  
526 537
-- | Split and join should always be idempotent.
538
prop_Utils_commaSplitJoin :: [Char] -> Property
527 539
prop_Utils_commaSplitJoin s =
528 540
  Utils.commaJoin (Utils.sepSplit ',' s) ==? s
529 541

  
530 542
-- | fromObjWithDefault, we test using the Maybe monad and an integer
531 543
-- value.
544
prop_Utils_fromObjWithDefault :: Integer -> String -> Bool
532 545
prop_Utils_fromObjWithDefault def_value random_key =
533 546
  -- a missing key will be returned with the default
534 547
  JSON.fromObjWithDefault [] random_key def_value == Just def_value &&
535 548
  -- a found key will be returned as is, not with default
536 549
  JSON.fromObjWithDefault [(random_key, J.showJSON def_value)]
537 550
       random_key (def_value+1) == Just def_value
538
    where _types = def_value :: Integer
539 551

  
540 552
-- | Test that functional if' behaves like the syntactic sugar if.
541 553
prop_Utils_if'if :: Bool -> Int -> Int -> Gen Prop
......
572 584
          tlist = zip (repeat True)  lst2
573 585
          cndlist = flist ++ tlist ++ [undefined]
574 586

  
587
prop_Utils_parseUnit :: NonNegative Int -> Property
575 588
prop_Utils_parseUnit (NonNegative n) =
576 589
  Utils.parseUnit (show n) ==? Types.Ok n .&&.
577 590
  Utils.parseUnit (show n ++ "m") ==? Types.Ok n .&&.
......
583 596
  printTestCase "Internal error/overflow?"
584 597
    (n_mb >=0 && n_gb >= 0 && n_tb >= 0) .&&.
585 598
  property (Types.isBad (Utils.parseUnit (show n ++ "x")::Types.Result Int))
586
  where _types = (n::Int)
587
        n_mb = (fromIntegral n::Rational) * 1000 * 1000 / 1024 / 1024
599
  where n_mb = (fromIntegral n::Rational) * 1000 * 1000 / 1024 / 1024
588 600
        n_gb = n_mb * 1000
589 601
        n_tb = n_gb * 1000
590 602

  
......
603 615
-- ** PeerMap tests
604 616

  
605 617
-- | Make sure add is idempotent.
618
prop_PeerMap_addIdempotent :: PeerMap.PeerMap
619
                           -> PeerMap.Key -> PeerMap.Elem -> Property
606 620
prop_PeerMap_addIdempotent pmap key em =
607 621
  fn puniq ==? fn (fn puniq)
608
    where _types = (pmap::PeerMap.PeerMap,
609
                    key::PeerMap.Key, em::PeerMap.Elem)
610
          fn = PeerMap.add key em
622
    where fn = PeerMap.add key em
611 623
          puniq = PeerMap.accumArray const pmap
612 624

  
613 625
-- | Make sure remove is idempotent.
626
prop_PeerMap_removeIdempotent :: PeerMap.PeerMap -> PeerMap.Key -> Property
614 627
prop_PeerMap_removeIdempotent pmap key =
615 628
  fn puniq ==? fn (fn puniq)
616
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
617
          fn = PeerMap.remove key
629
    where fn = PeerMap.remove key
618 630
          puniq = PeerMap.accumArray const pmap
619 631

  
620 632
-- | Make sure a missing item returns 0.
633
prop_PeerMap_findMissing :: PeerMap.PeerMap -> PeerMap.Key -> Property
621 634
prop_PeerMap_findMissing pmap key =
622 635
  PeerMap.find key (PeerMap.remove key puniq) ==? 0
623
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
624
          puniq = PeerMap.accumArray const pmap
636
    where puniq = PeerMap.accumArray const pmap
625 637

  
626 638
-- | Make sure an added item is found.
639
prop_PeerMap_addFind :: PeerMap.PeerMap
640
                     -> PeerMap.Key -> PeerMap.Elem -> Property
627 641
prop_PeerMap_addFind pmap key em =
628 642
  PeerMap.find key (PeerMap.add key em puniq) ==? em
629
    where _types = (pmap::PeerMap.PeerMap,
630
                    key::PeerMap.Key, em::PeerMap.Elem)
631
          puniq = PeerMap.accumArray const pmap
643
    where puniq = PeerMap.accumArray const pmap
632 644

  
633 645
-- | Manual check that maxElem returns the maximum indeed, or 0 for null.
646
prop_PeerMap_maxElem :: PeerMap.PeerMap -> Property
634 647
prop_PeerMap_maxElem pmap =
635 648
  PeerMap.maxElem puniq ==? if null puniq then 0
636 649
                              else (maximum . snd . unzip) puniq
637
    where _types = pmap::PeerMap.PeerMap
638
          puniq = PeerMap.accumArray const pmap
650
    where puniq = PeerMap.accumArray const pmap
639 651

  
640 652
-- | List of tests for the PeerMap module.
641 653
testSuite "PeerMap"
......
650 662

  
651 663
-- we silence the following due to hlint bug fixed in later versions
652 664
{-# ANN prop_Container_addTwo "HLint: ignore Avoid lambda" #-}
665
prop_Container_addTwo :: [Container.Key] -> Int -> Int -> Bool
653 666
prop_Container_addTwo cdata i1 i2 =
654 667
  fn i1 i2 cont == fn i2 i1 cont &&
655 668
  fn i1 i2 cont == fn i1 i2 (fn i1 i2 cont)
656
    where _types = (cdata::[Int],
657
                    i1::Int, i2::Int)
658
          cont = foldl (\c x -> Container.add x x c) Container.empty cdata
669
    where cont = foldl (\c x -> Container.add x x c) Container.empty cdata
659 670
          fn x1 x2 = Container.addTwo x1 x1 x2 x2
660 671

  
672
prop_Container_nameOf :: Node.Node -> Property
661 673
prop_Container_nameOf node =
662 674
  let nl = makeSmallCluster node 1
663 675
      fnode = head (Container.elems nl)
......
666 678
-- | We test that in a cluster, given a random node, we can find it by
667 679
-- its name and alias, as long as all names and aliases are unique,
668 680
-- and that we fail to find a non-existing name.
681
prop_Container_findByName :: Node.Node -> Property
669 682
prop_Container_findByName node =
670 683
  forAll (choose (1, 20)) $ \ cnt ->
671 684
  forAll (choose (0, cnt - 1)) $ \ fidx ->
......
694 707

  
695 708
-- Simple instance tests, we only have setter/getters
696 709

  
710
prop_Instance_creat :: Instance.Instance -> Property
697 711
prop_Instance_creat inst =
698 712
  Instance.name inst ==? Instance.alias inst
699 713

  
714
prop_Instance_setIdx :: Instance.Instance -> Types.Idx -> Property
700 715
prop_Instance_setIdx inst idx =
701 716
  Instance.idx (Instance.setIdx inst idx) ==? idx
702
    where _types = (inst::Instance.Instance, idx::Types.Idx)
703 717

  
718
prop_Instance_setName :: Instance.Instance -> String -> Bool
704 719
prop_Instance_setName inst name =
705 720
  Instance.name newinst == name &&
706 721
  Instance.alias newinst == name
707
    where _types = (inst::Instance.Instance, name::String)
708
          newinst = Instance.setName inst name
722
    where newinst = Instance.setName inst name
709 723

  
724
prop_Instance_setAlias :: Instance.Instance -> String -> Bool
710 725
prop_Instance_setAlias inst name =
711 726
  Instance.name newinst == Instance.name inst &&
712 727
  Instance.alias newinst == name
713
    where _types = (inst::Instance.Instance, name::String)
714
          newinst = Instance.setAlias inst name
728
    where newinst = Instance.setAlias inst name
715 729

  
730
prop_Instance_setPri :: Instance.Instance -> Types.Ndx -> Property
716 731
prop_Instance_setPri inst pdx =
717 732
  Instance.pNode (Instance.setPri inst pdx) ==? pdx
718
    where _types = (inst::Instance.Instance, pdx::Types.Ndx)
719 733

  
734
prop_Instance_setSec :: Instance.Instance -> Types.Ndx -> Property
720 735
prop_Instance_setSec inst sdx =
721 736
  Instance.sNode (Instance.setSec inst sdx) ==? sdx
722
    where _types = (inst::Instance.Instance, sdx::Types.Ndx)
723 737

  
738
prop_Instance_setBoth :: Instance.Instance -> Types.Ndx -> Types.Ndx -> Bool
724 739
prop_Instance_setBoth inst pdx sdx =
725 740
  Instance.pNode si == pdx && Instance.sNode si == sdx
726
    where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
727
          si = Instance.setBoth inst pdx sdx
741
    where si = Instance.setBoth inst pdx sdx
728 742

  
743
prop_Instance_shrinkMG :: Instance.Instance -> Property
729 744
prop_Instance_shrinkMG inst =
730 745
  Instance.mem inst >= 2 * Types.unitMem ==>
731 746
    case Instance.shrinkByType inst Types.FailMem of
732 747
      Types.Ok inst' -> Instance.mem inst' == Instance.mem inst - Types.unitMem
733 748
      _ -> False
734 749

  
750
prop_Instance_shrinkMF :: Instance.Instance -> Property
735 751
prop_Instance_shrinkMF inst =
736 752
  forAll (choose (0, 2 * Types.unitMem - 1)) $ \mem ->
737 753
    let inst' = inst { Instance.mem = mem}
738 754
    in Types.isBad $ Instance.shrinkByType inst' Types.FailMem
739 755

  
756
prop_Instance_shrinkCG :: Instance.Instance -> Property
740 757
prop_Instance_shrinkCG inst =
741 758
  Instance.vcpus inst >= 2 * Types.unitCpu ==>
742 759
    case Instance.shrinkByType inst Types.FailCPU of
......
744 761
        Instance.vcpus inst' == Instance.vcpus inst - Types.unitCpu
745 762
      _ -> False
746 763

  
764
prop_Instance_shrinkCF :: Instance.Instance -> Property
747 765
prop_Instance_shrinkCF inst =
748 766
  forAll (choose (0, 2 * Types.unitCpu - 1)) $ \vcpus ->
749 767
    let inst' = inst { Instance.vcpus = vcpus }
750 768
    in Types.isBad $ Instance.shrinkByType inst' Types.FailCPU
751 769

  
770
prop_Instance_shrinkDG :: Instance.Instance -> Property
752 771
prop_Instance_shrinkDG inst =
753 772
  Instance.dsk inst >= 2 * Types.unitDsk ==>
754 773
    case Instance.shrinkByType inst Types.FailDisk of
......
756 775
        Instance.dsk inst' == Instance.dsk inst - Types.unitDsk
757 776
      _ -> False
758 777

  
778
prop_Instance_shrinkDF :: Instance.Instance -> Property
759 779
prop_Instance_shrinkDF inst =
760 780
  forAll (choose (0, 2 * Types.unitDsk - 1)) $ \dsk ->
761 781
    let inst' = inst { Instance.dsk = dsk }
762 782
    in Types.isBad $ Instance.shrinkByType inst' Types.FailDisk
763 783

  
784
prop_Instance_setMovable :: Instance.Instance -> Bool -> Property
764 785
prop_Instance_setMovable inst m =
765 786
  Instance.movable inst' ==? m
766 787
    where inst' = Instance.setMovable inst m
......
788 809

  
789 810
-- Instance text loader tests
790 811

  
812
prop_Text_Load_Instance :: String -> Int -> Int -> Int -> Types.InstanceStatus
813
                        -> NonEmptyList Char -> [Char]
814
                        -> NonNegative Int -> NonNegative Int -> Bool
815
                        -> Types.DiskTemplate -> Int -> Property
791 816
prop_Text_Load_Instance name mem dsk vcpus status
792 817
                        (NonEmpty pnode) snode
793 818
                        (NonNegative pdx) (NonNegative sdx) autobal dt su =
......
810 835
      fail1 = Text.loadInst nl
811 836
              [name, mem_s, dsk_s, vcpus_s, status_s,
812 837
               sbal, pnode, pnode, tags]
813
      _types = ( name::String, mem::Int, dsk::Int
814
               , vcpus::Int, status::Types.InstanceStatus
815
               , snode::String
816
               , autobal::Bool)
817 838
  in case inst of
818 839
       Types.Bad msg -> failTest $ "Failed to load instance: " ++ msg
819 840
       Types.Ok (_, i) -> printTestCase "Mismatch in some field while\
......
829 850
               Instance.spindleUse i == su &&
830 851
               Types.isBad fail1
831 852

  
853
prop_Text_Load_InstanceFail :: [(String, Int)] -> [String] -> Property
832 854
prop_Text_Load_InstanceFail ktn fields =
833 855
  length fields /= 10 && length fields /= 11 ==>
834 856
    case Text.loadInst nl fields of
......
837 859
                       "Invalid/incomplete instance data: '" `isPrefixOf` msg
838 860
    where nl = Data.Map.fromList ktn
839 861

  
862
prop_Text_Load_Node :: String -> Int -> Int -> Int -> Int -> Int
863
                    -> Int -> Bool -> Bool
840 864
prop_Text_Load_Node name tm nm fm td fd tc fo =
841 865
  let conv v = if v < 0
842 866
                 then "?"
......
867 891
                Node.fDsk node == fd &&
868 892
                Node.tCpu node == fromIntegral tc
869 893

  
894
prop_Text_Load_NodeFail :: [String] -> Property
870 895
prop_Text_Load_NodeFail fields =
871 896
  length fields /= 8 ==> isNothing $ Text.loadNode Data.Map.empty fields
872 897

  
898
prop_Text_NodeLSIdempotent :: Node.Node -> Property
873 899
prop_Text_NodeLSIdempotent node =
874 900
  (Text.loadNode defGroupAssoc.
875 901
       Utils.sepSplit '|' . Text.serializeNode defGroupList) n ==?
......
878 904
    where n = Node.setPolicy Types.defIPolicy $
879 905
              node { Node.failN1 = True, Node.offline = False }
880 906

  
907
prop_Text_ISpecIdempotent :: Types.ISpec -> Property
881 908
prop_Text_ISpecIdempotent ispec =
882 909
  case Text.loadISpec "dummy" . Utils.sepSplit ',' .
883 910
       Text.serializeISpec $ ispec of
884 911
    Types.Bad msg -> failTest $ "Failed to load ispec: " ++ msg
885 912
    Types.Ok ispec' -> ispec ==? ispec'
886 913

  
914
prop_Text_IPolicyIdempotent :: Types.IPolicy -> Property
887 915
prop_Text_IPolicyIdempotent ipol =
888 916
  case Text.loadIPolicy . Utils.sepSplit '|' $
889 917
       Text.serializeIPolicy owner ipol of
......
898 926
-- allocations, not for the business logic). As such, it's a quite
899 927
-- complex and slow test, and that's the reason we restrict it to
900 928
-- small cluster sizes.
929
prop_Text_CreateSerialise :: Property
901 930
prop_Text_CreateSerialise =
902 931
  forAll genTags $ \ctags ->
903 932
  forAll (choose (1, 20)) $ \maxiter ->
......
955 984

  
956 985
-- | Checks that given a set of corrects specs, we can load them
957 986
-- successfully, and that at high-level the values look right.
987
prop_SimuLoad :: Property
958 988
prop_SimuLoad =
959 989
  forAll (choose (0, 10)) $ \ngroups ->
960 990
  forAll (replicateM ngroups genSimuSpec) $ \specs ->
......
964 994
      mdc_in = concatMap (\(_, n, d, m, c) ->
965 995
                            replicate n (fromIntegral m, fromIntegral d,
966 996
                                         fromIntegral c,
967
                                         fromIntegral m, fromIntegral d)) specs
997
                                         fromIntegral m, fromIntegral d))
998
               specs :: [(Double, Double, Double, Int, Int)]
968 999
  in case Simu.parseData strspecs of
969 1000
       Types.Bad msg -> failTest $ "Failed to load specs: " ++ msg
970 1001
       Types.Ok (Loader.ClusterData gl nl il tags ipol) ->
......
989 1020

  
990 1021
-- ** Node tests
991 1022

  
1023
prop_Node_setAlias :: Node.Node -> String -> Bool
992 1024
prop_Node_setAlias node name =
993 1025
  Node.name newnode == Node.name node &&
994 1026
  Node.alias newnode == name
995
    where _types = (node::Node.Node, name::String)
996
          newnode = Node.setAlias node name
1027
    where newnode = Node.setAlias node name
997 1028

  
1029
prop_Node_setOffline :: Node.Node -> Bool -> Property
998 1030
prop_Node_setOffline node status =
999 1031
  Node.offline newnode ==? status
1000 1032
    where newnode = Node.setOffline node status
1001 1033

  
1034
prop_Node_setXmem :: Node.Node -> Int -> Property
1002 1035
prop_Node_setXmem node xm =
1003 1036
  Node.xMem newnode ==? xm
1004 1037
    where newnode = Node.setXmem node xm
1005 1038

  
1039
prop_Node_setMcpu :: Node.Node -> Double -> Property
1006 1040
prop_Node_setMcpu node mc =
1007 1041
  Types.iPolicyVcpuRatio (Node.iPolicy newnode) ==? mc
1008 1042
    where newnode = Node.setMcpu node mc
1009 1043

  
1010 1044
-- | Check that an instance add with too high memory or disk will be
1011 1045
-- rejected.
1046
prop_Node_addPriFM :: Node.Node -> Instance.Instance -> Property
1012 1047
prop_Node_addPriFM node inst =
1013 1048
  Instance.mem inst >= Node.fMem node && not (Node.failN1 node) &&
1014 1049
  not (Instance.isOffline inst) ==>
1015 1050
  case Node.addPri node inst'' of
1016 1051
    Types.OpFail Types.FailMem -> True
1017 1052
    _ -> False
1018
  where _types = (node::Node.Node, inst::Instance.Instance)
1019
        inst' = setInstanceSmallerThanNode node inst
1053
  where inst' = setInstanceSmallerThanNode node inst
1020 1054
        inst'' = inst' { Instance.mem = Instance.mem inst }
1021 1055

  
1022 1056
-- | Check that adding a primary instance with too much disk fails
1023 1057
-- with type FailDisk.
1058
prop_Node_addPriFD :: Node.Node -> Instance.Instance -> Property
1024 1059
prop_Node_addPriFD node inst =
1025 1060
  forAll (elements Instance.localStorageTemplates) $ \dt ->
1026 1061
  Instance.dsk inst >= Node.fDsk node && not (Node.failN1 node) ==>
......
1033 1068

  
1034 1069
-- | Check that adding a primary instance with too many VCPUs fails
1035 1070
-- with type FailCPU.
1071
prop_Node_addPriFC :: Property
1036 1072
prop_Node_addPriFC =
1037 1073
  forAll (choose (1, maxCpu)) $ \extra ->
1038 1074
  forAll genOnlineNode $ \node ->
......
1045 1081

  
1046 1082
-- | Check that an instance add with too high memory or disk will be
1047 1083
-- rejected.
1084
prop_Node_addSec :: Node.Node -> Instance.Instance -> Int -> Property
1048 1085
prop_Node_addSec node inst pdx =
1049 1086
  ((Instance.mem inst >= (Node.fMem node - Node.rMem node) &&
1050 1087
    not (Instance.isOffline inst)) ||
1051 1088
   Instance.dsk inst >= Node.fDsk node) &&
1052 1089
  not (Node.failN1 node) ==>
1053 1090
      isFailure (Node.addSec node inst pdx)
1054
        where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
1055 1091

  
1056 1092
-- | Check that an offline instance with reasonable disk size but
1057 1093
-- extra mem/cpu can always be added.
1094
prop_Node_addOfflinePri :: NonNegative Int -> NonNegative Int -> Property
1058 1095
prop_Node_addOfflinePri (NonNegative extra_mem) (NonNegative extra_cpu) =
1059 1096
  forAll genOnlineNode $ \node ->
1060 1097
  forAll (genInstanceSmallerThanNode node) $ \inst ->
......
1067 1104

  
1068 1105
-- | Check that an offline instance with reasonable disk size but
1069 1106
-- extra mem/cpu can always be added.
1107
prop_Node_addOfflineSec :: NonNegative Int -> NonNegative Int
1108
                        -> Types.Ndx -> Property
1070 1109
prop_Node_addOfflineSec (NonNegative extra_mem) (NonNegative extra_cpu) pdx =
1071 1110
  forAll genOnlineNode $ \node ->
1072 1111
  forAll (genInstanceSmallerThanNode node) $ \inst ->
......
1079 1118
       v -> failTest $ "Expected OpGood/OpGood, but got: " ++ show v
1080 1119

  
1081 1120
-- | Checks for memory reservation changes.
1121
prop_Node_rMem :: Instance.Instance -> Property
1082 1122
prop_Node_rMem inst =
1083 1123
  not (Instance.isOffline inst) ==>
1084 1124
  forAll (genOnlineNode `suchThat` ((> Types.unitMem) . Node.fMem)) $ \node ->
......
1112 1152
       x -> failTest $ "Failed to add/remove instances: " ++ show x
1113 1153

  
1114 1154
-- | Check mdsk setting.
1155
prop_Node_setMdsk :: Node.Node -> SmallRatio -> Bool
1115 1156
prop_Node_setMdsk node mx =
1116 1157
  Node.loDsk node' >= 0 &&
1117 1158
  fromIntegral (Node.loDsk node') <= Node.tDsk node &&
......
1119 1160
  Node.availDisk node' <= Node.fDsk node' &&
1120 1161
  fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
1121 1162
  Node.mDsk node' == mx'
1122
    where _types = (node::Node.Node, mx::SmallRatio)
1123
          node' = Node.setMdsk node mx'
1163
    where node' = Node.setMdsk node mx'
1124 1164
          SmallRatio mx' = mx
1125 1165

  
1126 1166
-- Check tag maps
1167
prop_Node_tagMaps_idempotent :: Property
1127 1168
prop_Node_tagMaps_idempotent =
1128 1169
  forAll genTags $ \tags ->
1129 1170
  Node.delTags (Node.addTags m tags) tags ==? m
1130 1171
    where m = Data.Map.empty
1131 1172

  
1173
prop_Node_tagMaps_reject :: Property
1132 1174
prop_Node_tagMaps_reject =
1133 1175
  forAll (genTags `suchThat` (not . null)) $ \tags ->
1134 1176
  let m = Node.addTags Data.Map.empty tags
1135 1177
  in all (\t -> Node.rejectAddTags m [t]) tags
1136 1178

  
1179
prop_Node_showField :: Node.Node -> Property
1137 1180
prop_Node_showField node =
1138 1181
  forAll (elements Node.defaultFields) $ \ field ->
1139 1182
  fst (Node.showHeader field) /= Types.unknownField &&
1140 1183
  Node.showField node field /= Types.unknownField
1141 1184

  
1185
prop_Node_computeGroups :: [Node.Node] -> Bool
1142 1186
prop_Node_computeGroups nodes =
1143 1187
  let ng = Node.computeGroups nodes
1144 1188
      onlyuuid = map fst ng
......
1148 1192
     (null nodes || not (null ng))
1149 1193

  
1150 1194
-- Check idempotence of add/remove operations
1195
prop_Node_addPri_idempotent :: Property
1151 1196
prop_Node_addPri_idempotent =
1152 1197
  forAll genOnlineNode $ \node ->
1153 1198
  forAll (genInstanceSmallerThanNode node) $ \inst ->
......
1155 1200
    Types.OpGood node' -> Node.removePri node' inst ==? node
1156 1201
    _ -> failTest "Can't add instance"
1157 1202

  
1203
prop_Node_addSec_idempotent :: Property
1158 1204
prop_Node_addSec_idempotent =
1159 1205
  forAll genOnlineNode $ \node ->
1160 1206
  forAll (genInstanceSmallerThanNode node) $ \inst ->
......
1190 1236

  
1191 1237
-- | Check that the cluster score is close to zero for a homogeneous
1192 1238
-- cluster.
1239
prop_Score_Zero :: Node.Node -> Property
1193 1240
prop_Score_Zero node =
1194 1241
  forAll (choose (1, 1024)) $ \count ->
1195 1242
    (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
......
1202 1249
  in score <= 1e-12
1203 1250

  
1204 1251
-- | Check that cluster stats are sane.
1252
prop_CStats_sane :: Property
1205 1253
prop_CStats_sane =
1206 1254
  forAll (choose (1, 1024)) $ \count ->
1207 1255
  forAll genOnlineNode $ \node ->
......
1214 1262

  
1215 1263
-- | Check that one instance is allocated correctly, without
1216 1264
-- rebalances needed.
1265
prop_ClusterAlloc_sane :: Instance.Instance -> Property
1217 1266
prop_ClusterAlloc_sane inst =
1218 1267
  forAll (choose (5, 20)) $ \count ->
1219 1268
  forAll genOnlineNode $ \node ->
......
1234 1283
-- instance spec via tiered allocation (whatever the original instance
1235 1284
-- spec), on either one or two nodes. Furthermore, we test that
1236 1285
-- computed allocation statistics are correct.
1286
prop_ClusterCanTieredAlloc :: Instance.Instance -> Property
1237 1287
prop_ClusterCanTieredAlloc inst =
1238 1288
  forAll (choose (2, 5)) $ \count ->
1239 1289
  forAll (genOnlineNode `suchThat` (isNodeBig 4)) $ \node ->
......
1264 1314

  
1265 1315
-- | Helper function to create a cluster with the given range of nodes
1266 1316
-- and allocate an instance on it.
1317
genClusterAlloc :: Int -> Node.Node -> Instance.Instance
1318
                -> Types.Result (Node.List, Instance.List, Instance.Instance)
1267 1319
genClusterAlloc count node inst =
1268 1320
  let nl = makeSmallCluster node count
1269 1321
      reqnodes = Instance.requiredNodes $ Instance.diskTemplate inst
......
1279 1331

  
1280 1332
-- | Checks that on a 4-8 node cluster, once we allocate an instance,
1281 1333
-- we can also relocate it.
1334
prop_ClusterAllocRelocate :: Property
1282 1335
prop_ClusterAllocRelocate =
1283 1336
  forAll (choose (4, 8)) $ \count ->
1284 1337
  forAll (genOnlineNode `suchThat` (isNodeBig 4)) $ \node ->
......
1296 1349

  
1297 1350
-- | Helper property checker for the result of a nodeEvac or
1298 1351
-- changeGroup operation.
1352
check_EvacMode :: Group.Group -> Instance.Instance
1353
               -> Types.Result (Node.List, Instance.List, Cluster.EvacSolution)
1354
               -> Property
1299 1355
check_EvacMode grp inst result =
1300 1356
  case result of
1301 1357
    Types.Bad msg -> failTest $ "Couldn't evacuate/change group:" ++ msg
......
1311 1367
                               failmsg "wrong target group"
1312 1368
                                         (gdx == Group.idx grp)
1313 1369
           v -> failmsg  ("invalid solution: " ++ show v) False
1314
  where failmsg = \msg -> printTestCase ("Failed to evacuate: " ++ msg)
1370
  where failmsg :: String -> Bool -> Property
1371
        failmsg = \msg -> printTestCase ("Failed to evacuate: " ++ msg)
1315 1372
        idx = Instance.idx inst
1316 1373

  
1317 1374
-- | Checks that on a 4-8 node cluster, once we allocate an instance,
1318 1375
-- we can also node-evacuate it.
1376
prop_ClusterAllocEvacuate :: Property
1319 1377
prop_ClusterAllocEvacuate =
1320 1378
  forAll (choose (4, 8)) $ \count ->
1321 1379
  forAll (genOnlineNode `suchThat` (isNodeBig 4)) $ \node ->
......
1332 1390
-- | Checks that on a 4-8 node cluster with two node groups, once we
1333 1391
-- allocate an instance on the first node group, we can also change
1334 1392
-- its group.
1393
prop_ClusterAllocChangeGroup :: Property
1335 1394
prop_ClusterAllocChangeGroup =
1336 1395
  forAll (choose (4, 8)) $ \count ->
1337 1396
  forAll (genOnlineNode `suchThat` (isNodeBig 4)) $ \node ->
......
1353 1412

  
1354 1413
-- | Check that allocating multiple instances on a cluster, then
1355 1414
-- adding an empty node, results in a valid rebalance.
1415
prop_ClusterAllocBalance :: Property
1356 1416
prop_ClusterAllocBalance =
1357 1417
  forAll (genNode (Just 5) (Just 128)) $ \node ->
1358 1418
  forAll (choose (3, 5)) $ \count ->
......
1374 1434
            canBalance tbl True True False
1375 1435

  
1376 1436
-- | Checks consistency.
1437
prop_ClusterCheckConsistency :: Node.Node -> Instance.Instance -> Bool
1377 1438
prop_ClusterCheckConsistency node inst =
1378 1439
  let nl = makeSmallCluster node 3
1379 1440
      [node1, node2, node3] = Container.elems nl
......
1388 1449
     (not . null $ ccheck [(0, inst3)])
1389 1450

  
1390 1451
-- | For now, we only test that we don't lose instances during the split.
1452
prop_ClusterSplitCluster :: Node.Node -> Instance.Instance -> Property
1391 1453
prop_ClusterSplitCluster node inst =
1392 1454
  forAll (choose (0, 100)) $ \icnt ->
1393 1455
  let nl = makeSmallCluster node 2
......
1411 1473
           Just _ -> True
1412 1474

  
1413 1475
-- | Checks that allocation obeys minimum and maximum instance
1414
-- policies. The unittest generates a random node, duplicates it count
1476
-- policies. The unittest generates a random node, duplicates it /count/
1415 1477
-- times, and generates a random instance that can be allocated on
1416 1478
-- this mini-cluster; it then checks that after applying a policy that
1417 1479
-- the instance doesn't fits, the allocation fails.
1480
prop_ClusterAllocPolicy :: Node.Node -> Property
1418 1481
prop_ClusterAllocPolicy node =
1419 1482
  -- rqn is the required nodes (1 or 2)
1420 1483
  forAll (choose (1, 2)) $ \rqn ->
......
1444 1507
-- ** OpCodes tests
1445 1508

  
1446 1509
-- | Check that opcode serialization is idempotent.
1510
prop_OpCodes_serialization :: OpCodes.OpCode -> Property
1447 1511
prop_OpCodes_serialization op =
1448 1512
  case J.readJSON (J.showJSON op) of
1449 1513
    J.Error e -> failTest $ "Cannot deserialise: " ++ e
1450 1514
    J.Ok op' -> op ==? op'
1451
  where _types = op::OpCodes.OpCode
1452 1515

  
1453 1516
testSuite "OpCodes"
1454 1517
            [ 'prop_OpCodes_serialization ]
......
1456 1519
-- ** Jobs tests
1457 1520

  
1458 1521
-- | Check that (queued) job\/opcode status serialization is idempotent.
1522
prop_OpStatus_serialization :: Jobs.OpStatus -> Property
1459 1523
prop_OpStatus_serialization os =
1460 1524
  case J.readJSON (J.showJSON os) of
1461 1525
    J.Error e -> failTest $ "Cannot deserialise: " ++ e
1462 1526
    J.Ok os' -> os ==? os'
1463
  where _types = os::Jobs.OpStatus
1464 1527

  
1528
prop_JobStatus_serialization :: Jobs.JobStatus -> Property
1465 1529
prop_JobStatus_serialization js =
1466 1530
  case J.readJSON (J.showJSON js) of
1467 1531
    J.Error e -> failTest $ "Cannot deserialise: " ++ e
1468 1532
    J.Ok js' -> js ==? js'
1469
  where _types = js::Jobs.JobStatus
1470 1533

  
1471 1534
testSuite "Jobs"
1472 1535
            [ 'prop_OpStatus_serialization
......
1475 1538

  
1476 1539
-- ** Loader tests
1477 1540

  
1541
prop_Loader_lookupNode :: [(String, Int)] -> String -> String -> Property
1478 1542
prop_Loader_lookupNode ktn inst node =
1479 1543
  Loader.lookupNode nl inst node ==? Data.Map.lookup node nl
1480 1544
    where nl = Data.Map.fromList ktn
1481 1545

  
1546
prop_Loader_lookupInstance :: [(String, Int)] -> String -> Property
1482 1547
prop_Loader_lookupInstance kti inst =
1483 1548
  Loader.lookupInstance il inst ==? Data.Map.lookup inst il
1484 1549
    where il = Data.Map.fromList kti
1485 1550

  
1551
prop_Loader_assignIndices :: Property
1486 1552
prop_Loader_assignIndices =
1487 1553
  -- generate nodes with unique names
1488 1554
  forAll (arbitrary `suchThat`
......
1499 1565

  
1500 1566
-- | Checks that the number of primary instances recorded on the nodes
1501 1567
-- is zero.
1568
prop_Loader_mergeData :: [Node.Node] -> Bool
1502 1569
prop_Loader_mergeData ns =
1503 1570
  let na = Container.fromList $ map (\n -> (Node.idx n, n)) ns
1504 1571
  in case Loader.mergeData [] [] [] []
......
1533 1600

  
1534 1601
-- ** Types tests
1535 1602

  
1603
prop_Types_AllocPolicy_serialisation :: Types.AllocPolicy -> Property
1536 1604
prop_Types_AllocPolicy_serialisation apol =
1537 1605
  case J.readJSON (J.showJSON apol) of
1538 1606
    J.Ok p -> p ==? apol
1539 1607
    J.Error s -> failTest $ "Failed to deserialise: " ++ s
1540
      where _types = apol::Types.AllocPolicy
1541 1608

  
1609
prop_Types_DiskTemplate_serialisation :: Types.DiskTemplate -> Property
1542 1610
prop_Types_DiskTemplate_serialisation dt =
1543 1611
  case J.readJSON (J.showJSON dt) of
1544 1612
    J.Ok p -> p ==? dt
1545 1613
    J.Error s -> failTest $ "Failed to deserialise: " ++ s
1546
      where _types = dt::Types.DiskTemplate
1547 1614

  
1615
prop_Types_ISpec_serialisation :: Types.ISpec -> Property
1548 1616
prop_Types_ISpec_serialisation ispec =
1549 1617
  case J.readJSON (J.showJSON ispec) of
1550 1618
    J.Ok p -> p ==? ispec
1551 1619
    J.Error s -> failTest $ "Failed to deserialise: " ++ s
1552
      where _types = ispec::Types.ISpec
1553 1620

  
1621
prop_Types_IPolicy_serialisation :: Types.IPolicy -> Property
1554 1622
prop_Types_IPolicy_serialisation ipol =
1555 1623
  case J.readJSON (J.showJSON ipol) of
1556 1624
    J.Ok p -> p ==? ipol
1557 1625
    J.Error s -> failTest $ "Failed to deserialise: " ++ s
1558
      where _types = ipol::Types.IPolicy
1559 1626

  
1627
prop_Types_EvacMode_serialisation :: Types.EvacMode -> Property
1560 1628
prop_Types_EvacMode_serialisation em =
1561 1629
  case J.readJSON (J.showJSON em) of
1562 1630
    J.Ok p -> p ==? em
1563 1631
    J.Error s -> failTest $ "Failed to deserialise: " ++ s
1564
      where _types = em::Types.EvacMode
1565 1632

  
1633
prop_Types_opToResult :: Types.OpResult Int -> Bool
1566 1634
prop_Types_opToResult op =
1567 1635
  case op of
1568 1636
    Types.OpFail _ -> Types.isBad r
......
1570 1638
                        Types.Bad _ -> False
1571 1639
                        Types.Ok v' -> v == v'
1572 1640
  where r = Types.opToResult op
1573
        _types = op::Types.OpResult Int
1574 1641

  
1642
prop_Types_eitherToResult :: Either String Int -> Bool
1575 1643
prop_Types_eitherToResult ei =
1576 1644
  case ei of
1577 1645
    Left _ -> Types.isBad r
......
1579 1647
                 Types.Bad _ -> False
1580 1648
                 Types.Ok v' -> v == v'
1581 1649
    where r = Types.eitherToResult ei
1582
          _types = ei::Either String Int
1583 1650

  
1584 1651
testSuite "Types"
1585 1652
            [ 'prop_Types_AllocPolicy_serialisation
......
1594 1661
-- ** CLI tests
1595 1662

  
1596 1663
-- | Test correct parsing.
1664
prop_CLI_parseISpec :: String -> Int -> Int -> Int -> Property
1597 1665
prop_CLI_parseISpec descr dsk mem cpu =
1598
  let str = printf "%d,%d,%d" dsk mem cpu
1666
  let str = printf "%d,%d,%d" dsk mem cpu::String
1599 1667
  in CLI.parseISpecString descr str ==? Types.Ok (Types.RSpec cpu mem dsk)
1600 1668

  
1601 1669
-- | Test parsing failure due to wrong section count.
1670
prop_CLI_parseISpecFail :: String -> Property
1602 1671
prop_CLI_parseISpecFail descr =
1603 1672
  forAll (choose (0,100) `suchThat` ((/=) 3)) $ \nelems ->
1604 1673
  forAll (replicateM nelems arbitrary) $ \values ->
......
1608 1677
       _ -> property True
1609 1678

  
1610 1679
-- | Test parseYesNo.
1680
prop_CLI_parseYesNo :: Bool -> Bool -> [Char] -> Property
1611 1681
prop_CLI_parseYesNo def testval val =
1612 1682
  forAll (elements [val, "yes", "no"]) $ \actual_val ->
1613 1683
  if testval
......
1618 1688
              else property $ Types.isBad result
1619 1689

  
1620 1690
-- | Helper to check for correct parsing of string arg.
1691
checkStringArg :: [Char]
1692
               -> (GetOpt.OptDescr (CLI.Options -> Types.Result CLI.Options),
1693
                   CLI.Options -> Maybe [Char])
1694
               -> Property
1621 1695
checkStringArg val (opt, fn) =
1622 1696
  let GetOpt.Option _ longs _ _ = opt
1623 1697
  in case longs of
......
1628 1702
           Right (options, _) -> fn options ==? Just val
1629 1703

  
1630 1704
-- | Test a few string arguments.
1705
prop_CLI_StringArg :: [Char] -> Property
1631 1706
prop_CLI_StringArg argument =
1632 1707
  let args = [ (CLI.oDataFile,      CLI.optDataFile)
1633 1708
             , (CLI.oDynuFile,      CLI.optDynuFile)
......
1639 1714
  in conjoin $ map (checkStringArg argument) args
1640 1715

  
1641 1716
-- | Helper to test that a given option is accepted OK with quick exit.
1717
checkEarlyExit :: String -> [CLI.OptType] -> String -> Property
1642 1718
checkEarlyExit name options param =
1643 1719
  case CLI.parseOptsInner [param] name options of
1644 1720
    Left (code, _) -> if code == 0
......
1651 1727

  
1652 1728
-- | Test that all binaries support some common options. There is
1653 1729
-- nothing actually random about this test...
1730
prop_CLI_stdopts :: Property
1654 1731
prop_CLI_stdopts =
1655 1732
  let params = ["-h", "--help", "-V", "--version"]
1656 1733
      opts = map (\(name, (_, o)) -> (name, o)) Program.personalities
......
1739 1816
instance Arbitrary Ssconf.SSKey where
1740 1817
  arbitrary = elements [minBound..maxBound]
1741 1818

  
1819
prop_Ssconf_filename :: Ssconf.SSKey -> Property
1742 1820
prop_Ssconf_filename key =
1743 1821
  printTestCase "Key doesn't start with correct prefix" $
1744 1822
    Ssconf.sSFilePrefix `isPrefixOf` Ssconf.keyToFilename (Just "") key

Also available in: Unified diff