Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / QC.hs @ 434c15d5

History | View | Annotate | Download (8.6 kB)

1
{-| Unittests for ganeti-htools
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2009 Google Inc.
8

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

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

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

    
24
-}
25

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

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

    
50
-- | Simple checker for whether OpResult is fail or pass
51
isFailure :: Types.OpResult a -> Bool
52
isFailure (Types.OpFail _) = True
53
isFailure _ = False
54

    
55
-- copied from the introduction to quickcheck
56
instance Arbitrary Char where
57
    arbitrary = choose ('\32', '\128')
58

    
59
-- let's generate a random instance
60
instance Arbitrary Instance.Instance where
61
    arbitrary = do
62
      name <- arbitrary
63
      mem <- choose(0, 100)
64
      dsk <- choose(0, 100)
65
      run_st <- elements ["ERROR_up", "ERROR_down", "ADMIN_down"
66
                         , "ERROR_nodedown", "ERROR_nodeoffline"
67
                         , "running"
68
                         , "no_such_status1", "no_such_status2"]
69
      pn <- arbitrary
70
      sn <- arbitrary
71
      vcpus <- arbitrary
72
      return $ Instance.create name mem dsk vcpus run_st [] pn sn
73

    
74
-- and a random node
75
instance Arbitrary Node.Node where
76
    arbitrary = do
77
      name <- arbitrary
78
      mem_t <- arbitrary
79
      mem_f <- choose (0, mem_t)
80
      mem_n <- choose (0, mem_t - mem_f)
81
      dsk_t <- arbitrary
82
      dsk_f <- choose (0, dsk_t)
83
      cpu_t <- arbitrary
84
      offl <- arbitrary
85
      let n = Node.create name (fromIntegral mem_t) mem_n mem_f
86
              (fromIntegral dsk_t) dsk_f cpu_t offl
87
          n' = Node.buildPeers n Container.empty
88
      return n'
89

    
90
-- | Make sure add is idempotent
91
prop_PeerMap_addIdempotent pmap key em =
92
    fn puniq == fn (fn puniq)
93
    where _types = (pmap::PeerMap.PeerMap,
94
                    key::PeerMap.Key, em::PeerMap.Elem)
95
          fn = PeerMap.add key em
96
          puniq = PeerMap.accumArray const pmap
97

    
98
-- | Make sure remove is idempotent
99
prop_PeerMap_removeIdempotent pmap key =
100
    fn puniq == fn (fn puniq)
101
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
102
          fn = PeerMap.remove key
103
          puniq = PeerMap.accumArray const pmap
104

    
105
-- | Make sure a missing item returns 0
106
prop_PeerMap_findMissing pmap key =
107
    PeerMap.find key (PeerMap.remove key puniq) == 0
108
    where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
109
          puniq = PeerMap.accumArray const pmap
110

    
111
-- | Make sure an added item is found
112
prop_PeerMap_addFind pmap key em =
113
    PeerMap.find key (PeerMap.add key em puniq) == em
114
    where _types = (pmap::PeerMap.PeerMap,
115
                    key::PeerMap.Key, em::PeerMap.Elem)
116
          puniq = PeerMap.accumArray const pmap
117

    
118
-- | Manual check that maxElem returns the maximum indeed, or 0 for null
119
prop_PeerMap_maxElem pmap =
120
    PeerMap.maxElem puniq == if null puniq then 0
121
                             else (maximum . snd . unzip) puniq
122
    where _types = pmap::PeerMap.PeerMap
123
          puniq = PeerMap.accumArray const pmap
124

    
125
testPeerMap =
126
    [ run prop_PeerMap_addIdempotent
127
    , run prop_PeerMap_removeIdempotent
128
    , run prop_PeerMap_maxElem
129
    , run prop_PeerMap_addFind
130
    , run prop_PeerMap_findMissing
131
    ]
132

    
133
-- Container tests
134

    
135
prop_Container_addTwo cdata i1 i2 =
136
    fn i1 i2 cont == fn i2 i1 cont &&
137
       fn i1 i2 cont == fn i1 i2 (fn i1 i2 cont)
138
    where _types = (cdata::[Int],
139
                    i1::Int, i2::Int)
140
          cont = foldl (\c x -> Container.add x x c) Container.empty cdata
141
          fn x1 x2 = Container.addTwo x1 x1 x2 x2
142

    
143
testContainer =
144
    [ run prop_Container_addTwo ]
145

    
146
-- Simple instance tests, we only have setter/getters
147

    
148
prop_Instance_setIdx inst idx =
149
    Instance.idx (Instance.setIdx inst idx) == idx
150
    where _types = (inst::Instance.Instance, idx::Types.Idx)
151

    
152
prop_Instance_setName inst name =
153
    Instance.name (Instance.setName inst name) == name
154
    where _types = (inst::Instance.Instance, name::String)
155

    
156
prop_Instance_setPri inst pdx =
157
    Instance.pNode (Instance.setPri inst pdx) == pdx
158
    where _types = (inst::Instance.Instance, pdx::Types.Ndx)
159

    
160
prop_Instance_setSec inst sdx =
161
    Instance.sNode (Instance.setSec inst sdx) == sdx
162
    where _types = (inst::Instance.Instance, sdx::Types.Ndx)
163

    
164
prop_Instance_setBoth inst pdx sdx =
165
    Instance.pNode si == pdx && Instance.sNode si == sdx
166
    where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
167
          si = Instance.setBoth inst pdx sdx
168

    
169
prop_Instance_runStatus_True inst =
170
    let run_st = Instance.running inst
171
        run_tx = Instance.runSt inst
172
    in
173
      run_tx == "running" || run_tx == "ERROR_up" ==> run_st
174

    
175
prop_Instance_runStatus_False inst =
176
    let run_st = Instance.running inst
177
        run_tx = Instance.runSt inst
178
    in
179
      run_tx /= "running" && run_tx /= "ERROR_up" ==> not run_st
180

    
181
testInstance =
182
    [ run prop_Instance_setIdx
183
    , run prop_Instance_setName
184
    , run prop_Instance_setPri
185
    , run prop_Instance_setSec
186
    , run prop_Instance_setBoth
187
    , run prop_Instance_runStatus_True
188
    , run prop_Instance_runStatus_False
189
    ]
190

    
191
-- Instance text loader tests
192

    
193
prop_Text_Load_Instance name mem dsk vcpus status pnode snode pdx sdx =
194
    let vcpus_s = show vcpus
195
        dsk_s = show dsk
196
        mem_s = show mem
197
        rsnode = snode ++ "a" -- non-empty secondary node
198
        rsdx = if pdx == sdx
199
               then sdx + 1
200
               else sdx
201
        ndx = [(pnode, pdx), (rsnode, rsdx)]
202
        tags = ""
203
        inst = Text.loadInst ndx
204
               [name, mem_s, dsk_s, vcpus_s, status, pnode, rsnode, tags]::
205
               Maybe (String, Instance.Instance)
206
        _types = ( name::String, mem::Int, dsk::Int
207
                 , vcpus::Int, status::String
208
                 , pnode::String, snode::String
209
                 , pdx::Types.Ndx, sdx::Types.Ndx)
210
    in
211
      case inst of
212
        Nothing -> False
213
        Just (_, i) ->
214
            (Instance.name i == name &&
215
             Instance.vcpus i == vcpus &&
216
             Instance.mem i == mem &&
217
             Instance.pNode i == pdx &&
218
             Instance.sNode i == rsdx)
219

    
220
testText =
221
    [ run prop_Text_Load_Instance
222
    ]
223

    
224
-- Node tests
225

    
226
-- | Check that an instance add with too high memory or disk will be rejected
227
prop_Node_addPri node inst = (Instance.mem inst >= Node.fMem node ||
228
                              Instance.dsk inst >= Node.fDsk node) &&
229
                             not (Node.failN1 node)
230
                             ==>
231
                             isFailure (Node.addPri node inst)
232
    where _types = (node::Node.Node, inst::Instance.Instance)
233

    
234

    
235
-- | Check that an instance add with too high memory or disk will be rejected
236
prop_Node_addSec node inst pdx =
237
    (Instance.mem inst >= (Node.fMem node - Node.rMem node) ||
238
     Instance.dsk inst >= Node.fDsk node) &&
239
    not (Node.failN1 node)
240
    ==> isFailure (Node.addSec node inst pdx)
241
        where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
242

    
243
testNode =
244
    [ run prop_Node_addPri
245
    , run prop_Node_addSec
246
    ]
247

    
248

    
249
-- Cluster tests
250

    
251
-- | Check that the cluster score is close to zero for a homogeneous cluster
252
prop_Score_Zero node count =
253
    (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
254
     (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
255
    let fn = Node.buildPeers node Container.empty
256
        nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
257
        nl = Container.fromAssocList nlst
258
        score = Cluster.compCV nl
259
    -- we can't say == 0 here as the floating point errors accumulate;
260
    -- this should be much lower than the default score in CLI.hs
261
    in score <= 1e-15
262

    
263
testCluster =
264
    [ run prop_Score_Zero
265
    ]