Statistics
| Branch: | Tag: | Revision:

root / htest / Test / Ganeti / HTools / Text.hs @ 26d62e4c

History | View | Annotate | Download (8 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2
{-# OPTIONS_GHC -fno-warn-orphans #-}
3

    
4
{-| Unittests for ganeti-htools.
5

    
6
-}
7

    
8
{-
9

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

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

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

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

    
27
-}
28

    
29
module Test.Ganeti.HTools.Text (testHTools_Text) where
30

    
31
import Test.QuickCheck
32

    
33
import qualified Data.Map as Map
34
import Data.List
35
import Data.Maybe
36

    
37
import Test.Ganeti.TestHelper
38
import Test.Ganeti.TestCommon
39
import Test.Ganeti.TestHTools
40
import Test.Ganeti.HTools.Instance (genInstanceSmallerThanNode)
41
import Test.Ganeti.HTools.Node (genNode, genOnlineNode)
42

    
43
import qualified Ganeti.HTools.Cluster as Cluster
44
import qualified Ganeti.HTools.Container as Container
45
import qualified Ganeti.HTools.Group as Group
46
import qualified Ganeti.HTools.Instance as Instance
47
import qualified Ganeti.HTools.Loader as Loader
48
import qualified Ganeti.HTools.Node as Node
49
import qualified Ganeti.HTools.Text as Text
50
import qualified Ganeti.HTools.Types as Types
51
import qualified Ganeti.Utils as Utils
52

    
53
-- * Instance text loader tests
54

    
55
prop_Load_Instance :: String -> Int -> Int -> Int -> Types.InstanceStatus
56
                   -> NonEmptyList Char -> String
57
                   -> NonNegative Int -> NonNegative Int -> Bool
58
                   -> Types.DiskTemplate -> Int -> Property
59
prop_Load_Instance name mem dsk vcpus status
60
                   (NonEmpty pnode) snode
61
                   (NonNegative pdx) (NonNegative sdx) autobal dt su =
62
  pnode /= snode && pdx /= sdx ==>
63
  let vcpus_s = show vcpus
64
      dsk_s = show dsk
65
      mem_s = show mem
66
      su_s = show su
67
      status_s = Types.instanceStatusToRaw status
68
      ndx = if null snode
69
              then [(pnode, pdx)]
70
              else [(pnode, pdx), (snode, sdx)]
71
      nl = Map.fromList ndx
72
      tags = ""
73
      sbal = if autobal then "Y" else "N"
74
      sdt = Types.diskTemplateToRaw dt
75
      inst = Text.loadInst nl
76
             [name, mem_s, dsk_s, vcpus_s, status_s,
77
              sbal, pnode, snode, sdt, tags, su_s]
78
      fail1 = Text.loadInst nl
79
              [name, mem_s, dsk_s, vcpus_s, status_s,
80
               sbal, pnode, pnode, tags]
81
  in case inst of
82
       Types.Bad msg -> failTest $ "Failed to load instance: " ++ msg
83
       Types.Ok (_, i) -> printTestCase "Mismatch in some field while\
84
                                        \ loading the instance" $
85
               Instance.name i == name &&
86
               Instance.vcpus i == vcpus &&
87
               Instance.mem i == mem &&
88
               Instance.pNode i == pdx &&
89
               Instance.sNode i == (if null snode
90
                                      then Node.noSecondary
91
                                      else sdx) &&
92
               Instance.autoBalance i == autobal &&
93
               Instance.spindleUse i == su &&
94
               Types.isBad fail1
95

    
96
prop_Load_InstanceFail :: [(String, Int)] -> [String] -> Property
97
prop_Load_InstanceFail ktn fields =
98
  length fields /= 10 && length fields /= 11 ==>
99
    case Text.loadInst nl fields of
100
      Types.Ok _ -> failTest "Managed to load instance from invalid data"
101
      Types.Bad msg -> printTestCase ("Unrecognised error message: " ++ msg) $
102
                       "Invalid/incomplete instance data: '" `isPrefixOf` msg
103
    where nl = Map.fromList ktn
104

    
105
prop_Load_Node :: String -> Int -> Int -> Int -> Int -> Int
106
               -> Int -> Bool -> Bool
107
prop_Load_Node name tm nm fm td fd tc fo =
108
  let conv v = if v < 0
109
                 then "?"
110
                 else show v
111
      tm_s = conv tm
112
      nm_s = conv nm
113
      fm_s = conv fm
114
      td_s = conv td
115
      fd_s = conv fd
116
      tc_s = conv tc
117
      fo_s = if fo
118
               then "Y"
119
               else "N"
120
      any_broken = any (< 0) [tm, nm, fm, td, fd, tc]
121
      gid = Group.uuid defGroup
122
  in case Text.loadNode defGroupAssoc
123
       [name, tm_s, nm_s, fm_s, td_s, fd_s, tc_s, fo_s, gid] of
124
       Nothing -> False
125
       Just (name', node) ->
126
         if fo || any_broken
127
           then Node.offline node
128
           else Node.name node == name' && name' == name &&
129
                Node.alias node == name &&
130
                Node.tMem node == fromIntegral tm &&
131
                Node.nMem node == nm &&
132
                Node.fMem node == fm &&
133
                Node.tDsk node == fromIntegral td &&
134
                Node.fDsk node == fd &&
135
                Node.tCpu node == fromIntegral tc
136

    
137
prop_Load_NodeFail :: [String] -> Property
138
prop_Load_NodeFail fields =
139
  length fields /= 8 ==> isNothing $ Text.loadNode Map.empty fields
140

    
141
prop_NodeLSIdempotent :: Property
142
prop_NodeLSIdempotent =
143
  forAll (genNode (Just 1) Nothing) $ \node ->
144
  -- override failN1 to what loadNode returns by default
145
  let n = Node.setPolicy Types.defIPolicy $
146
          node { Node.failN1 = True, Node.offline = False }
147
  in
148
    (Text.loadNode defGroupAssoc.
149
         Utils.sepSplit '|' . Text.serializeNode defGroupList) n ==?
150
    Just (Node.name n, n)
151

    
152
prop_ISpecIdempotent :: Types.ISpec -> Property
153
prop_ISpecIdempotent ispec =
154
  case Text.loadISpec "dummy" . Utils.sepSplit ',' .
155
       Text.serializeISpec $ ispec of
156
    Types.Bad msg -> failTest $ "Failed to load ispec: " ++ msg
157
    Types.Ok ispec' -> ispec ==? ispec'
158

    
159
prop_IPolicyIdempotent :: Types.IPolicy -> Property
160
prop_IPolicyIdempotent ipol =
161
  case Text.loadIPolicy . Utils.sepSplit '|' $
162
       Text.serializeIPolicy owner ipol of
163
    Types.Bad msg -> failTest $ "Failed to load ispec: " ++ msg
164
    Types.Ok res -> (owner, ipol) ==? res
165
  where owner = "dummy"
166

    
167
-- | This property, while being in the text tests, does more than just
168
-- test end-to-end the serialisation and loading back workflow; it
169
-- also tests the Loader.mergeData and the actuall
170
-- Cluster.iterateAlloc (for well-behaving w.r.t. instance
171
-- allocations, not for the business logic). As such, it's a quite
172
-- complex and slow test, and that's the reason we restrict it to
173
-- small cluster sizes.
174
prop_CreateSerialise :: Property
175
prop_CreateSerialise =
176
  forAll genTags $ \ctags ->
177
  forAll (choose (1, 20)) $ \maxiter ->
178
  forAll (choose (2, 10)) $ \count ->
179
  forAll genOnlineNode $ \node ->
180
  forAll (genInstanceSmallerThanNode node) $ \inst ->
181
  let nl = makeSmallCluster node count
182
      reqnodes = Instance.requiredNodes $ Instance.diskTemplate inst
183
  in case Cluster.genAllocNodes defGroupList nl reqnodes True >>= \allocn ->
184
     Cluster.iterateAlloc nl Container.empty (Just maxiter) inst allocn [] []
185
     of
186
       Types.Bad msg -> failTest $ "Failed to allocate: " ++ msg
187
       Types.Ok (_, _, _, [], _) -> printTestCase
188
                                    "Failed to allocate: no allocations" False
189
       Types.Ok (_, nl', il', _, _) ->
190
         let cdata = Loader.ClusterData defGroupList nl' il' ctags
191
                     Types.defIPolicy
192
             saved = Text.serializeCluster cdata
193
         in case Text.parseData saved >>= Loader.mergeData [] [] [] [] of
194
              Types.Bad msg -> failTest $ "Failed to load/merge: " ++ msg
195
              Types.Ok (Loader.ClusterData gl2 nl2 il2 ctags2 cpol2) ->
196
                ctags ==? ctags2 .&&.
197
                Types.defIPolicy ==? cpol2 .&&.
198
                il' ==? il2 .&&.
199
                defGroupList ==? gl2 .&&.
200
                nl' ==? nl2
201

    
202
testSuite "HTools/Text"
203
            [ 'prop_Load_Instance
204
            , 'prop_Load_InstanceFail
205
            , 'prop_Load_Node
206
            , 'prop_Load_NodeFail
207
            , 'prop_NodeLSIdempotent
208
            , 'prop_ISpecIdempotent
209
            , 'prop_IPolicyIdempotent
210
            , 'prop_CreateSerialise
211
            ]