Statistics
| Branch: | Tag: | Revision:

root / test / hs / Test / Ganeti / Locking / Allocation.hs @ 9f9c4543

History | View | Annotate | Download (10.8 kB)

1 3469663d Klaus Aehlig
{-# LANGUAGE TemplateHaskell #-}
2 3469663d Klaus Aehlig
{-# OPTIONS_GHC -fno-warn-orphans #-}
3 3469663d Klaus Aehlig
4 3469663d Klaus Aehlig
{-| Tests for lock allocation.
5 3469663d Klaus Aehlig
6 3469663d Klaus Aehlig
-}
7 3469663d Klaus Aehlig
8 3469663d Klaus Aehlig
{-
9 3469663d Klaus Aehlig
10 3469663d Klaus Aehlig
Copyright (C) 2014 Google Inc.
11 3469663d Klaus Aehlig
12 3469663d Klaus Aehlig
This program is free software; you can redistribute it and/or modify
13 3469663d Klaus Aehlig
it under the terms of the GNU General Public License as published by
14 3469663d Klaus Aehlig
the Free Software Foundation; either version 2 of the License, or
15 3469663d Klaus Aehlig
(at your option) any later version.
16 3469663d Klaus Aehlig
17 3469663d Klaus Aehlig
This program is distributed in the hope that it will be useful, but
18 3469663d Klaus Aehlig
WITHOUT ANY WARRANTY; without even the implied warranty of
19 3469663d Klaus Aehlig
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 3469663d Klaus Aehlig
General Public License for more details.
21 3469663d Klaus Aehlig
22 3469663d Klaus Aehlig
You should have received a copy of the GNU General Public License
23 3469663d Klaus Aehlig
along with this program; if not, write to the Free Software
24 3469663d Klaus Aehlig
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25 3469663d Klaus Aehlig
02110-1301, USA.
26 3469663d Klaus Aehlig
27 3469663d Klaus Aehlig
-}
28 3469663d Klaus Aehlig
29 3469663d Klaus Aehlig
module Test.Ganeti.Locking.Allocation (testLocking_Allocation) where
30 3469663d Klaus Aehlig
31 3469663d Klaus Aehlig
import Control.Applicative
32 9bf17b50 Klaus Aehlig
import qualified Data.Foldable as F
33 3469663d Klaus Aehlig
import qualified Data.Map as M
34 3469663d Klaus Aehlig
import qualified Data.Set as S
35 3469663d Klaus Aehlig
36 3469663d Klaus Aehlig
import Test.QuickCheck
37 3469663d Klaus Aehlig
38 987fcfb7 Klaus Aehlig
import Test.Ganeti.TestCommon
39 3469663d Klaus Aehlig
import Test.Ganeti.TestHelper
40 3469663d Klaus Aehlig
41 1d49428b Klaus Aehlig
import Ganeti.BasicTypes
42 3469663d Klaus Aehlig
import Ganeti.Locking.Allocation
43 619b12df Klaus Aehlig
import Ganeti.Locking.Types
44 3469663d Klaus Aehlig
45 3469663d Klaus Aehlig
{-
46 3469663d Klaus Aehlig
47 3469663d Klaus Aehlig
Ganeti.Locking.Allocation is polymorphic in the types of locks
48 3469663d Klaus Aehlig
and lock owners. So we can use much simpler types here than Ganeti's
49 3469663d Klaus Aehlig
real locks and lock owners, knowning at polymorphic functions cannot
50 3469663d Klaus Aehlig
exploit the simplicity of the types they're deling with.
51 3469663d Klaus Aehlig
52 3469663d Klaus Aehlig
-}
53 3469663d Klaus Aehlig
54 3469663d Klaus Aehlig
data TestOwner = TestOwner Int deriving (Ord, Eq, Show)
55 3469663d Klaus Aehlig
56 3469663d Klaus Aehlig
instance Arbitrary TestOwner where
57 619b12df Klaus Aehlig
  arbitrary = TestOwner <$> choose (0, 2)
58 3469663d Klaus Aehlig
59 619b12df Klaus Aehlig
data TestLock = TestBigLock
60 619b12df Klaus Aehlig
              | TestCollectionLockA
61 619b12df Klaus Aehlig
              | TestLockA Int
62 619b12df Klaus Aehlig
              | TestCollectionLockB
63 619b12df Klaus Aehlig
              | TestLockB Int
64 619b12df Klaus Aehlig
              deriving (Ord, Eq, Show)
65 3469663d Klaus Aehlig
66 3469663d Klaus Aehlig
instance Arbitrary TestLock where
67 619b12df Klaus Aehlig
  arbitrary =  frequency [ (1, elements [ TestBigLock
68 619b12df Klaus Aehlig
                                        , TestCollectionLockA
69 619b12df Klaus Aehlig
                                        , TestCollectionLockB
70 619b12df Klaus Aehlig
                                        ])
71 619b12df Klaus Aehlig
                         , (2, TestLockA <$> choose (0, 2))
72 619b12df Klaus Aehlig
                         , (2, TestLockB <$> choose (0, 2))
73 619b12df Klaus Aehlig
                         ]
74 619b12df Klaus Aehlig
75 619b12df Klaus Aehlig
instance Lock TestLock where
76 619b12df Klaus Aehlig
  lockImplications (TestLockA _) = [TestCollectionLockA, TestBigLock]
77 619b12df Klaus Aehlig
  lockImplications (TestLockB _) = [TestCollectionLockB, TestBigLock]
78 619b12df Klaus Aehlig
  lockImplications TestBigLock = []
79 619b12df Klaus Aehlig
  lockImplications _ = [TestBigLock]
80 3469663d Klaus Aehlig
81 3469663d Klaus Aehlig
{-
82 3469663d Klaus Aehlig
83 07eec0fc Klaus Aehlig
All states of a  LockAllocation every available outside the
84 07eec0fc Klaus Aehlig
Ganeti.Locking.Allocation module must be constructed by starting
85 07eec0fc Klaus Aehlig
with emptyAllocation and applying the exported functions.
86 3469663d Klaus Aehlig
87 3469663d Klaus Aehlig
-}
88 3469663d Klaus Aehlig
89 3469663d Klaus Aehlig
instance Arbitrary OwnerState where
90 3469663d Klaus Aehlig
  arbitrary = elements [OwnShared, OwnExclusive]
91 3469663d Klaus Aehlig
92 3469663d Klaus Aehlig
instance Arbitrary a => Arbitrary (LockRequest a) where
93 3469663d Klaus Aehlig
  arbitrary = LockRequest <$> arbitrary <*> genMaybe arbitrary
94 3469663d Klaus Aehlig
95 07eec0fc Klaus Aehlig
data UpdateRequest b a = UpdateRequest b [LockRequest a]
96 07eec0fc Klaus Aehlig
                       | IntersectRequest b [a]
97 9f9c4543 Klaus Aehlig
                       | OpportunisticUnion b [(a, OwnerState)]
98 07eec0fc Klaus Aehlig
                       | FreeLockRequest b
99 07eec0fc Klaus Aehlig
                       deriving Show
100 3469663d Klaus Aehlig
101 3469663d Klaus Aehlig
instance (Arbitrary a, Arbitrary b) => Arbitrary (UpdateRequest a b) where
102 07eec0fc Klaus Aehlig
  arbitrary =
103 07eec0fc Klaus Aehlig
    frequency [ (4, UpdateRequest <$> arbitrary <*> (choose (1, 4) >>= vector))
104 07eec0fc Klaus Aehlig
              , (2, IntersectRequest <$> arbitrary
105 07eec0fc Klaus Aehlig
                                     <*> (choose (1, 4) >>= vector))
106 9f9c4543 Klaus Aehlig
              , (2, OpportunisticUnion <$> arbitrary
107 9f9c4543 Klaus Aehlig
                                       <*> (choose (1, 4) >>= vector))
108 07eec0fc Klaus Aehlig
              , (1, FreeLockRequest <$> arbitrary)
109 07eec0fc Klaus Aehlig
              ]
110 07eec0fc Klaus Aehlig
111 07eec0fc Klaus Aehlig
-- | Transform an UpdateRequest into the corresponding state transformer.
112 07eec0fc Klaus Aehlig
asAllocTrans :: (Lock a, Ord b, Show b)
113 07eec0fc Klaus Aehlig
              => LockAllocation a b -> UpdateRequest b a -> LockAllocation a b
114 07eec0fc Klaus Aehlig
asAllocTrans state (UpdateRequest owner updates) =
115 07eec0fc Klaus Aehlig
  fst $ updateLocks owner updates state
116 07eec0fc Klaus Aehlig
asAllocTrans state (IntersectRequest owner locks) =
117 07eec0fc Klaus Aehlig
  intersectLocks owner locks state
118 9f9c4543 Klaus Aehlig
asAllocTrans state (OpportunisticUnion owner locks) =
119 9f9c4543 Klaus Aehlig
  fst $ opportunisticLockUnion owner locks state
120 07eec0fc Klaus Aehlig
asAllocTrans state (FreeLockRequest owner) = freeLocks state owner
121 07eec0fc Klaus Aehlig
122 07eec0fc Klaus Aehlig
-- | Fold a sequence of requests to transform a lock allocation onto the empty
123 07eec0fc Klaus Aehlig
-- allocation. As we consider all exported LockAllocation transformers, any
124 07eec0fc Klaus Aehlig
-- LockAllocation definable is obtained in this way.
125 619b12df Klaus Aehlig
foldUpdates :: (Lock a, Ord b, Show b)
126 07eec0fc Klaus Aehlig
            => [UpdateRequest b a] -> LockAllocation a b
127 07eec0fc Klaus Aehlig
foldUpdates = foldl asAllocTrans emptyAllocation
128 3469663d Klaus Aehlig
129 619b12df Klaus Aehlig
instance (Arbitrary a, Lock a, Arbitrary b, Ord b, Show b)
130 3469663d Klaus Aehlig
          => Arbitrary (LockAllocation a b) where
131 07eec0fc Klaus Aehlig
  arbitrary = foldUpdates <$> (choose (0, 8) >>= vector)
132 3469663d Klaus Aehlig
133 3469663d Klaus Aehlig
-- | Basic property of locking: the exclusive locks of one user
134 3469663d Klaus Aehlig
-- are disjoint from any locks of any other user.
135 3469663d Klaus Aehlig
prop_LocksDisjoint :: Property
136 3469663d Klaus Aehlig
prop_LocksDisjoint =
137 3469663d Klaus Aehlig
  forAll (arbitrary :: Gen (LockAllocation TestLock TestOwner)) $ \state ->
138 3469663d Klaus Aehlig
  forAll (arbitrary :: Gen TestOwner) $ \a ->
139 3469663d Klaus Aehlig
  forAll (arbitrary `suchThat` (/= a)) $ \b ->
140 3469663d Klaus Aehlig
  let aExclusive = M.keysSet . M.filter (== OwnExclusive) $ listLocks  a state
141 3469663d Klaus Aehlig
      bAll = M.keysSet $ listLocks b state
142 3469663d Klaus Aehlig
  in printTestCase
143 3469663d Klaus Aehlig
     (show a ++ "'s exclusive lock" ++ " is not respected by " ++ show b)
144 3469663d Klaus Aehlig
     (S.null $ S.intersection aExclusive bAll)
145 3469663d Klaus Aehlig
146 277c969f Klaus Aehlig
-- | Verify that exclusive group locks are honored, i.e., verify that if someone
147 277c969f Klaus Aehlig
-- holds a lock, then no one else can hold a lock on an exclusive lock on an
148 277c969f Klaus Aehlig
-- implied lock.
149 277c969f Klaus Aehlig
prop_LockImplicationX :: Property
150 277c969f Klaus Aehlig
prop_LockImplicationX =
151 277c969f Klaus Aehlig
  forAll (arbitrary :: Gen (LockAllocation TestLock TestOwner)) $ \state ->
152 277c969f Klaus Aehlig
  forAll (arbitrary :: Gen TestOwner) $ \a ->
153 277c969f Klaus Aehlig
  forAll (arbitrary `suchThat` (/= a)) $ \b ->
154 277c969f Klaus Aehlig
  let bExclusive = M.keysSet . M.filter (== OwnExclusive) $ listLocks  b state
155 277c969f Klaus Aehlig
  in printTestCase "Others cannot have an exclusive lock on an implied lock" .
156 277c969f Klaus Aehlig
     flip all (M.keys $ listLocks a state) $ \lock ->
157 277c969f Klaus Aehlig
     flip all (lockImplications lock) $ \impliedlock ->
158 277c969f Klaus Aehlig
     not $ S.member impliedlock bExclusive
159 277c969f Klaus Aehlig
160 277c969f Klaus Aehlig
-- | Verify that shared group locks are honored, i.e., verify that if someone
161 277c969f Klaus Aehlig
-- holds an exclusive lock, then no one else can hold any form on lock on an
162 277c969f Klaus Aehlig
-- implied lock.
163 277c969f Klaus Aehlig
prop_LockImplicationS :: Property
164 277c969f Klaus Aehlig
prop_LockImplicationS =
165 277c969f Klaus Aehlig
  forAll (arbitrary :: Gen (LockAllocation TestLock TestOwner)) $ \state ->
166 277c969f Klaus Aehlig
  forAll (arbitrary :: Gen TestOwner) $ \a ->
167 277c969f Klaus Aehlig
  forAll (arbitrary `suchThat` (/= a)) $ \b ->
168 277c969f Klaus Aehlig
  let aExclusive = M.keys . M.filter (== OwnExclusive) $ listLocks  a state
169 277c969f Klaus Aehlig
      bAll = M.keysSet $ listLocks b state
170 277c969f Klaus Aehlig
  in printTestCase "Others cannot hold locks implied by an exclusive lock" .
171 277c969f Klaus Aehlig
     flip all aExclusive $ \lock ->
172 277c969f Klaus Aehlig
     flip all (lockImplications lock) $ \impliedlock ->
173 277c969f Klaus Aehlig
     not $ S.member impliedlock bAll
174 277c969f Klaus Aehlig
175 987fcfb7 Klaus Aehlig
-- | Verify that locks can only be modified by updates of the owner.
176 987fcfb7 Klaus Aehlig
prop_LocksStable :: Property
177 987fcfb7 Klaus Aehlig
prop_LocksStable =
178 987fcfb7 Klaus Aehlig
  forAll (arbitrary :: Gen (LockAllocation TestLock TestOwner)) $ \state ->
179 987fcfb7 Klaus Aehlig
  forAll (arbitrary :: Gen TestOwner) $ \a ->
180 987fcfb7 Klaus Aehlig
  forAll (arbitrary `suchThat` (/= a)) $ \b ->
181 987fcfb7 Klaus Aehlig
  forAll (arbitrary :: Gen [LockRequest TestLock]) $ \request ->
182 987fcfb7 Klaus Aehlig
  let (state', _) = updateLocks b request state
183 987fcfb7 Klaus Aehlig
  in (listLocks a state ==? listLocks a state')
184 987fcfb7 Klaus Aehlig
185 1d49428b Klaus Aehlig
-- | Verify that a given request is statisfied in list of owned locks
186 1d49428b Klaus Aehlig
requestSucceeded :: Ord a => M.Map a  OwnerState -> LockRequest a -> Bool
187 1d49428b Klaus Aehlig
requestSucceeded owned (LockRequest lock status) = M.lookup lock owned == status
188 1d49428b Klaus Aehlig
189 1d49428b Klaus Aehlig
-- | Verify that lock updates are atomic, i.e., either we get all the required
190 1d49428b Klaus Aehlig
-- locks, or the state is completely unchanged.
191 1d49428b Klaus Aehlig
prop_LockupdateAtomic :: Property
192 1d49428b Klaus Aehlig
prop_LockupdateAtomic =
193 1d49428b Klaus Aehlig
  forAll (arbitrary :: Gen (LockAllocation TestLock TestOwner)) $ \state ->
194 1d49428b Klaus Aehlig
  forAll (arbitrary :: Gen TestOwner) $ \a ->
195 1d49428b Klaus Aehlig
  forAll (arbitrary :: Gen [LockRequest TestLock]) $ \request ->
196 1d49428b Klaus Aehlig
  let (state', result) = updateLocks a request state
197 a317d77a Klaus Aehlig
  in if result == Ok S.empty
198 1d49428b Klaus Aehlig
       then printTestCase
199 a9c3ba29 Klaus Aehlig
            ("Update succeeded, but in final state " ++ show state'
200 1d49428b Klaus Aehlig
              ++ "not all locks are as requested")
201 1d49428b Klaus Aehlig
            $ let owned = listLocks a state'
202 1d49428b Klaus Aehlig
              in all (requestSucceeded owned) request
203 1d49428b Klaus Aehlig
       else printTestCase
204 1d49428b Klaus Aehlig
            ("Update failed, but state changed to " ++ show state')
205 1d49428b Klaus Aehlig
            (state == state')
206 1d49428b Klaus Aehlig
207 a9c3ba29 Klaus Aehlig
-- | Verify that releasing a lock always succeeds.
208 a9c3ba29 Klaus Aehlig
prop_LockReleaseSucceeds :: Property
209 a9c3ba29 Klaus Aehlig
prop_LockReleaseSucceeds =
210 a9c3ba29 Klaus Aehlig
  forAll (arbitrary :: Gen (LockAllocation TestLock TestOwner)) $ \state ->
211 a9c3ba29 Klaus Aehlig
  forAll (arbitrary :: Gen TestOwner) $ \a ->
212 a9c3ba29 Klaus Aehlig
  forAll (arbitrary :: Gen TestLock) $ \lock ->
213 a9c3ba29 Klaus Aehlig
  let (_, result) = updateLocks a [requestRelease lock] state
214 a9c3ba29 Klaus Aehlig
  in printTestCase
215 a9c3ba29 Klaus Aehlig
     ("Releasing a lock has to suceed uncondiationally, but got "
216 a9c3ba29 Klaus Aehlig
       ++ show result)
217 a9c3ba29 Klaus Aehlig
     (isOk result)
218 a9c3ba29 Klaus Aehlig
219 9bf17b50 Klaus Aehlig
-- | Verify the property that only the blocking owners prevent
220 9bf17b50 Klaus Aehlig
-- lock allocation. We deliberatly go for the expensive variant
221 9bf17b50 Klaus Aehlig
-- restraining by suchThat, as otherwise the number of cases actually
222 9bf17b50 Klaus Aehlig
-- covered is too small.
223 9bf17b50 Klaus Aehlig
prop_BlockSufficient :: Property
224 9bf17b50 Klaus Aehlig
prop_BlockSufficient =
225 9bf17b50 Klaus Aehlig
  forAll (arbitrary :: Gen TestOwner) $ \a ->
226 9bf17b50 Klaus Aehlig
  forAll (arbitrary :: Gen TestLock) $ \lock ->
227 9bf17b50 Klaus Aehlig
  forAll (elements [ [requestShared lock]
228 9bf17b50 Klaus Aehlig
                   , [requestExclusive lock]]) $ \request ->
229 9bf17b50 Klaus Aehlig
  forAll ((arbitrary :: Gen (LockAllocation TestLock TestOwner))
230 9bf17b50 Klaus Aehlig
           `suchThat` (genericResult (const False) (not . S.null)
231 9bf17b50 Klaus Aehlig
                        . snd . updateLocks a request)) $ \state ->
232 9bf17b50 Klaus Aehlig
  let (_, result) = updateLocks a request state
233 9bf17b50 Klaus Aehlig
      blockedOn = genericResult (const S.empty) id result
234 9bf17b50 Klaus Aehlig
  in  printTestCase "After all blockers release, a request must succeed"
235 9bf17b50 Klaus Aehlig
      . isOk . snd . updateLocks a request $ F.foldl freeLocks state blockedOn
236 9bf17b50 Klaus Aehlig
237 64df329d Klaus Aehlig
-- | Verify the property that every blocking owner is necessary, i.e., even
238 64df329d Klaus Aehlig
-- if we only keep the locks of one of the blocking owners, the request still
239 64df329d Klaus Aehlig
-- will be blocked. We deliberatly use the expensive variant of restraining
240 64df329d Klaus Aehlig
-- to ensure good coverage. To make sure the request can always be blocked
241 64df329d Klaus Aehlig
-- by two owners, for a shared request we request two different locks.
242 64df329d Klaus Aehlig
prop_BlockNecessary :: Property
243 64df329d Klaus Aehlig
prop_BlockNecessary =
244 64df329d Klaus Aehlig
  forAll (arbitrary :: Gen TestOwner) $ \a ->
245 64df329d Klaus Aehlig
  forAll (arbitrary :: Gen TestLock) $ \lock ->
246 64df329d Klaus Aehlig
  forAll (arbitrary `suchThat` (/= lock)) $ \lock' ->
247 64df329d Klaus Aehlig
  forAll (elements [ [requestShared lock, requestShared lock']
248 64df329d Klaus Aehlig
                   , [requestExclusive lock]]) $ \request ->
249 64df329d Klaus Aehlig
  forAll ((arbitrary :: Gen (LockAllocation TestLock TestOwner))
250 64df329d Klaus Aehlig
           `suchThat` (genericResult (const False) ((>= 2) . S.size)
251 64df329d Klaus Aehlig
                        . snd . updateLocks a request)) $ \state ->
252 64df329d Klaus Aehlig
  let (_, result) = updateLocks a request state
253 64df329d Klaus Aehlig
      blockers = genericResult (const S.empty) id result
254 64df329d Klaus Aehlig
  in  printTestCase "Each blocker alone must block the request"
255 64df329d Klaus Aehlig
      . flip all (S.elems blockers) $ \blocker ->
256 64df329d Klaus Aehlig
        (==) (Ok $ S.singleton blocker) . snd . updateLocks a request
257 64df329d Klaus Aehlig
        . F.foldl freeLocks state
258 64df329d Klaus Aehlig
        $ S.filter (/= blocker) blockers
259 64df329d Klaus Aehlig
260 3469663d Klaus Aehlig
testSuite "Locking/Allocation"
261 3469663d Klaus Aehlig
 [ 'prop_LocksDisjoint
262 277c969f Klaus Aehlig
 , 'prop_LockImplicationX
263 277c969f Klaus Aehlig
 , 'prop_LockImplicationS
264 987fcfb7 Klaus Aehlig
 , 'prop_LocksStable
265 1d49428b Klaus Aehlig
 , 'prop_LockupdateAtomic
266 a9c3ba29 Klaus Aehlig
 , 'prop_LockReleaseSucceeds
267 9bf17b50 Klaus Aehlig
 , 'prop_BlockSufficient
268 64df329d Klaus Aehlig
 , 'prop_BlockNecessary
269 3469663d Klaus Aehlig
 ]